Merge branch 'master' into feature-78257/StudVW_Finetuning

This commit is contained in:
Harald Bamberger
2026-08-11 10:49:05 +02:00
48 changed files with 3205 additions and 343 deletions
@@ -197,3 +197,11 @@ $config['unterbrechung_enabled'] = true;
*/
$config['wiederholung_enabled'] = true;
/**
* Modal with Infotext of TLC
* if true, there will be shown an info modal in the studentsView after the first button ABMELDUNG
*
* @var boolean
*/
$config['showInfoModalAbmeldung'] = true;
+2
View File
@@ -177,6 +177,8 @@ $config['stv_prestudent_tags'] = [
'unterbrecher_auto' => ['readonly' => true],
'stbtr_erh_auto' => ['readonly' => true],
'jgv_auto' => ['readonly' => true],
'in_auto' => ['readonly' => true],
'preabbr_auto' => ['readonly' => true],
];
$config['userAutomatedTags'] = 'sftest';
+2 -2
View File
@@ -22,10 +22,10 @@ if (!defined('BASEPATH')) exit('No direct script access allowed');
$config['root'] = [
'stg' => [
'semester' => [
/*'group',
'group',
'verband' => [
'group'
]*/
]
],
'orgform'
]
@@ -0,0 +1,162 @@
<?php
/**
* Copyright (C) 2024 fhcomplete.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
if (! defined('BASEPATH')) exit('No direct script access allowed');
/**
* This controller operates between (interface) the JS (GUI) and the PhrasesLib (back-end)
* Provides data to the ajax get calls about the Phrasen plugin
* This controller works with JSON calls on the HTTP GET and the output is always JSON
*/
class TabulatorPresets extends FHCAPI_Controller
{
public function __construct()
{
parent::__construct([
'getTabulatorPresets' => self::PERM_LOGGED,
'createTabulatorPreset' => self::PERM_LOGGED,
'updateTabulatorPreset' => self::PERM_LOGGED,
'deleteTabulatorPreset' => self::PERM_LOGGED,
]);
$this->load->model('tabulator/Tabulator_preset_model', 'TabulatorPresetModel');
$this->loadPhrases([
'global',
'tabulator_presets'
]);
}
//------------------------------------------------------------------------------------------------------------------
// Public methods
public function getTabulatorPresets()
{
$tableName = $this->input->get("tableName");
if (!$tableName) {
$this->terminateWithError(error($this->p->t("tabulator_presets", "invalid_params_err_msg")));
}
$uid = getAuthUID();
$presets = $this->TabulatorPresetModel->getTabulatorPresetsByUserAndTable($uid, $tableName);
$presetsData = $this->getDataOrTerminateWithError($presets) ?? [];
$this->terminateWithSuccess($presetsData);
}
public function createTabulatorPreset()
{
$tableName = $this->input->post("tableName");
$presetName = $this->input->post("presetName");
$preset = $this->input->post("preset");
if (!$tableName || !$presetName || !$preset) {
$this->terminateWithError($this->p->t("tabulator_presets", "invalid_params_err_msg"), "general", 400);
}
$uid = getAuthUID();
$existingPresetsResult = $this->TabulatorPresetModel->getTabulatorPresetsByUserAndTable($uid, $tableName);
$existingPresets = $this->getDataOrTerminateWithError($existingPresetsResult) ?? [];
if (count($existingPresets) > 19) {
$this->terminateWithError($this->p->t("tabulator_presets", "max_presets_count_err_msg"), "general", 409);
}
$existingPresetNames = array_map(function($presetInfo) {
return $presetInfo->preset_name;
}, $existingPresets);
if (in_array($presetName, $existingPresetNames)) {
$this->terminateWithError($this->p->t("tabulator_presets", "preset_name_duplicate_err_msg"), "general", 409);
}
$presetCreationResult = $this->TabulatorPresetModel->createTabulatorPreset($uid, $tableName, $presetName, $preset);
if (isError($presetCreationResult)) {
$this->terminateWithError($presetCreationResult->retval);
}
$newPresetData = $this->TabulatorPresetModel->getTabulatorPreset($presetCreationResult->retval);
$newPresetArray = $this->getDataOrTerminateWithError($newPresetData) ?? [null];
$newPreset = $newPresetArray[0];
$this->terminateWithSuccess($newPreset);
}
public function updateTabulatorPreset()
{
$presetId = $this->input->post("presetId");
$preset = $this->input->post("preset");
if (!$presetId || !$preset) {
$this->terminateWithError($this->p->t("tabulator_presets", "invalid_params_err_msg"), "general", 400);
}
$existingPresetData = $this->TabulatorPresetModel->getTabulatorPreset($presetId);
$existingPresetArray = $this->getDataOrTerminateWithError($existingPresetData) ?? [null];
$existingPreset = $existingPresetArray[0];
if (!$existingPreset) {
$this->terminateWithError($this->p->t("tabulator_presets", "preset_not_found_err_msg"), "general", 404);
}
$uid = getAuthUID();
if ($existingPreset->benutzer_uid !== $uid) {
$this->terminateWithError($this->p->t("tabulator_presets", "preset_not_own_update_err_msg"), "general", 403);
}
$presetUpdateResult = $this->TabulatorPresetModel->updateTabulatorPreset($presetId, $preset);
if (isError($presetUpdateResult)) {
$this->terminateWithError($presetUpdateResult->retval);
}
$updatedPresetData = $this->TabulatorPresetModel->getTabulatorPreset($presetId);
$updatedPresetArray = $this->getDataOrTerminateWithError($updatedPresetData) ?? [null];
$updatedPreset = $updatedPresetArray[0];
$this->terminateWithSuccess($updatedPreset);
}
public function deleteTabulatorPreset()
{
$presetId = $this->input->post("presetId");
if (!$presetId) {
$this->terminateWithError($this->p->t("tabulator_presets", "invalid_params_err_msg"), "general", 400);
}
$presetData = $this->TabulatorPresetModel->getTabulatorPreset($presetId);
$presetArray = $this->getDataOrTerminateWithError($presetData) ?? [null];
$preset = $presetArray[0];
if (!$preset) {
$this->terminateWithError($this->p->t("tabulator_presets", "preset_not_found_err_msg"), "general", 404);
}
$uid = getAuthUID();
if ($preset->benutzer_uid !== $uid) {
$this->terminateWithError($this->p->t("tabulator_presets", "preset_not_own_delete_err_msg"), "general", 403);
}
$presetDeletionResult = $this->TabulatorPresetModel->deleteTabulatorPreset($presetId);
if (isError($presetDeletionResult)) {
$this->terminateWithError($presetDeletionResult->retval);
}
$this->terminateWithSuccess();
}
}
@@ -756,6 +756,8 @@ class Students extends FHCAPI_Controller
'studiensemester_kurzbz' => $studiensemester_kurzbz
));
$studiensemester_kurzbz = $this->getStudiensemesterKurzbz($studiensemester_kurzbz);
$this->load->library('SearchLib', [ 'config' => 'searchstv' ]);
$this->load->library('form_validation');
@@ -14,11 +14,11 @@ class Mylv extends Auth_Controller
public function __construct()
{
parent::__construct([
'Student' => ['student/anrechnung_beantragen:r','user:r'], // TODO(chris): permissions?
'Studiensemester' => ['student/anrechnung_beantragen:r','user:r'], // TODO(chris): permissions?
'Lvs' => ['student/anrechnung_beantragen:r','user:r'], // TODO(chris): permissions?
'Info' => ['student/anrechnung_beantragen:r','user:r'], // TODO(chris): permissions?
'Pruefungen' => ['student/anrechnung_beantragen:r','user:r'] // TODO(chris): permissions?
'Student' => self::PERM_LOGGED,
'Studiensemester' => self::PERM_LOGGED,
'Lvs' => self::PERM_LOGGED,
'Info' => self::PERM_LOGGED,
'Pruefungen' => self::PERM_LOGGED
]);
}
+1 -1
View File
@@ -125,7 +125,7 @@ class AntragJob extends JOB_Controller
$stgLeitungen[$leitung->uid]['stgs'][] = $antrag->studiengang_kz;
}
$result = $this->StudierendenantragModel->getStgAndSem($antrag->studierendenantrag_id);
$result = $this->StudierendenantragModel->getStgAndDetails($antrag->studierendenantrag_id);
if (isError($result))
{
$this->logError(getError($result));
@@ -25,7 +25,8 @@ class Studierendenantrag extends FHC_Controller
// Load language phrases
$this->loadPhrases([
'studierendenantrag'
'studierendenantrag',
'ui'
]);
$this->load->config('studierendenantrag');
+17 -6
View File
@@ -349,16 +349,27 @@ class Tag_Controller extends FHCAPI_Controller
public function rebuildTagsForTypeId()
{
$id = $this->input->post('id');
$ids = $this->input->post('ids');
$typeId = $this->input->post('typeId');
$semester = $this->input->post('sem');
$result = $this->taglib->rebuildTagsForTypeId($typeId, $id, $semester);
$idsSuccess = [];
$idsError = [];
$outputResult = [];
if (isError($result))
return error ('Error occurred during updateAutomatedTags');
foreach ($ids as $id)
{
$result = $this->taglib->rebuildTagsForTypeId($typeId, $id, $semester);
$this->terminateWithSuccess($result);
if (isError($result))
$idsError[] = $id;
else
$idsSuccess[] = $id;
$outputResult[] = $result;
}
$this->terminateWithSuccess([$outputResult, $idsSuccess, $idsError]);
}
private function _setAuthUID()
@@ -401,4 +412,4 @@ class Tag_Controller extends FHCAPI_Controller
));
}
}
}
+2 -1
View File
@@ -111,7 +111,8 @@ function generateJSDataStorageObject($indexPage, $calledPath, $calledMethod)
'theme' => [
'name'=>$ci->config->item('theme_name'),
'modes'=>$ci->config->item('theme_modes'),
]
],
'domain' => DOMAIN
);
$toPrint = "\n";
+1 -1
View File
@@ -37,7 +37,7 @@ class TagLib
$this->_ci->load->library('PermissionLib');
$this->_ci->load->library('PrestudentLib');
$batchUser = $this->_ci->config->item('userAutomatedTags');
$this->batchUser = $this->_ci->config->item('userAutomatedTags');
}
public function updateAutomatedTags($paramsTag)
@@ -0,0 +1,63 @@
<?php
/**
* Description of out_auto
*
* @author ma0068
*/
class CoreIncomingTagLib
{
protected $ci;
public function __construct()
{
$this->ci = get_instance();
$this->ci->load->model('codex/Bisio_model', 'BisioModel');
}
public function getZuordnungIds(array $params)
{
if(!isset($params['studiensemester_kurzbz']))
{
return (object) array(
'idArray' => []
);
}
$semester = $params['studiensemester_kurzbz'];
$result = $this->ci->BisioModel->getIncomingsOfSemester($semester);
$data = $result->retval;
$outgoing_data = array_map(function($item) {
return [
'id' => $item->prestudent_id,
'von' => $item->von,
'bis' => $item->bis
];
}, $data);
return (object) array(
'data' => $outgoing_data,
'typeId' => 'prestudent_id',
);
}
public function isCriteriaSetFor(array $params)
{
if ( !isset($params['id'], $params['studiensemester_kurzbz'], $params['typeId']) || $params['typeId'] !== 'prestudent_id')
return false;
$semester = $params['studiensemester_kurzbz'];
$prestudent_id = $params['id'];
$result = $this->ci->BisioModel->isPrestudentIncoming($semester, $prestudent_id);
if (hasData($result)) {
return $result;
}
else
return null;
}
}
@@ -0,0 +1,80 @@
<?php
/**
* Description of prewh_auto
*
* @author ma0068
*/
class CorePreabbrecherTagLib
{
protected $ci;
const TYP_ABMELDUNG = ['Abmeldung', 'AbmeldungStgl'];
public function __construct()
{
$this->ci = get_instance();
$this->ci->load->model('education/Studierendenantrag_model', 'StudierendenantragModel');
}
public function getZuordnungIds(array $params)
{
if(!isset($params['studiensemester_kurzbz']))
{
return (object) array(
'idArray' => []
);
}
$semester = $params['studiensemester_kurzbz'];
$this->ci->StudierendenantragModel->addJoin('public.tbl_studiensemester', 'studiensemester_kurzbz');
$this->ci->StudierendenantragModel->db->where_in('typ', self::TYP_ABMELDUNG);
$this->ci->StudierendenantragModel->db->where('studiensemester_kurzbz', $semester);
$result = $this->ci->StudierendenantragModel->getWithLastStatusWhere([
'studierendenantrag_statustyp_kurzbz' => Studierendenantragstatus_model::STATUS_CREATED
]);
$data = $result->retval;
$prewiederholer_data = array_map(function($item) {
return [
'id' => $item->prestudent_id,
'von' => $item->start,
'bis' => $item->ende
];
}, $data);
return (object) array(
'data' => $prewiederholer_data,
'typeId' => 'prestudent_id',
);
}
public function isCriteriaSetFor(array $params)
{
if ( !isset($params['id'], $params['studiensemester_kurzbz'], $params['typeId']) || $params['typeId'] !== 'prestudent_id')
return false;
$semester = $params['studiensemester_kurzbz'];
$prestudent_id = $params['id'];
$this->ci->StudierendenantragModel->addSelect('prestudent_id');
$this->ci->StudierendenantragModel->addSelect('start as von');
$this->ci->StudierendenantragModel->addSelect('ende as bis');
$this->ci->StudierendenantragModel->addJoin('public.tbl_studiensemester', 'studiensemester_kurzbz');
$this->ci->StudierendenantragModel->db->where_in('typ', self::TYP_ABMELDUNG);
$this->ci->StudierendenantragModel->db->where('studiensemester_kurzbz', $semester);
$this->ci->StudierendenantragModel->db->where('prestudent_id', $prestudent_id);
$result = $this->ci->StudierendenantragModel->getWithLastStatusWhere([
'studierendenantrag_statustyp_kurzbz' => Studierendenantragstatus_model::STATUS_CREATED
]);
if(hasData($result))
{
return $result;
}
else
return null;
}
}
+53
View File
@@ -73,6 +73,34 @@ class Bisio_model extends DB_Model
return $this->execQuery($query, array($studiensemester_kurzbz));
}
/**
* Gets incoming students of certain Semester
* @param String studiensemester_kurzbz
* @return array of prestudent_ids
*/
public function getIncomingsOfSemester($studiensemester_kurzbz)
{
$query = "
SELECT DISTINCT ps.prestudent_id, tbl_bisio.von, tbl_bisio.bis
FROM bis.tbl_bisio
JOIN public.tbl_student USING (student_uid)
JOIN public.tbl_prestudent ps USING (prestudent_id)
JOIN public.tbl_prestudentstatus pss ON (ps.prestudent_id = pss.prestudent_id)
JOIN public.tbl_studiensemester ss ON (pss.studiensemester_kurzbz = ss.studiensemester_kurzbz)
WHERE ss.studiensemester_kurzbz = ?
AND nation_code = 'A'
AND (
tbl_bisio.von <= ss.ende
AND (
tbl_bisio.bis >= ss.start
OR tbl_bisio.bis IS NULL
)
)
";
return $this->execQuery($query, array($studiensemester_kurzbz));
}
public function isPrestudentOutgoing($studiensemester_kurzbz, $prestudent_id)
{
$query = "
@@ -97,4 +125,29 @@ class Bisio_model extends DB_Model
return $this->execQuery($query, array($studiensemester_kurzbz, $prestudent_id));
}
public function isPrestudentIncoming($studiensemester_kurzbz, $prestudent_id)
{
$query = "
SELECT
ps.prestudent_id, tbl_bisio.von, tbl_bisio.bis
FROM bis.tbl_bisio
JOIN public.tbl_student USING (student_uid)
JOIN public.tbl_prestudent ps USING (prestudent_id)
JOIN public.tbl_prestudentstatus pss ON (ps.prestudent_id = pss.prestudent_id)
JOIN public.tbl_studiensemester ss ON (pss.studiensemester_kurzbz = ss.studiensemester_kurzbz)
WHERE ss.studiensemester_kurzbz = ?
AND nation_code = 'A'
AND (
tbl_bisio.von <= ss.ende
AND (
tbl_bisio.bis >= ss.start
OR tbl_bisio.bis IS NULL
)
)
AND ps.prestudent_id = ?
";
return $this->execQuery($query, array($studiensemester_kurzbz, $prestudent_id));
}
}
@@ -188,6 +188,7 @@ class Studierendenantrag_model extends DB_Model
* Get the studiengang and ausbildungssemester the student was in
* for the studiensemester the antrag was committed for
*
*
* @param integer $antrag_id
*
* @return stdClass
@@ -242,6 +243,28 @@ class Studierendenantrag_model extends DB_Model
);
}
/**
* Get the studiengang and the studienplan the student is in
*
* @param integer $antrag_id
*
* @return stdClass
*/
public function getStgAndDetails($antrag_id)
{
$this->addSelect('p.studiengang_kz');
$this->addSelect('stg.orgform_kurzbz');
$this->addSelect('stg.bezeichnung');
$this->addJoin('public.tbl_prestudent p', 'prestudent_id');
$this->addJoin('public.tbl_studiengang stg', 'studiengang_kz');
$this->addLimit(1);
return $this->load(
$antrag_id
);
}
public function getStgEmail($antrag_id)
{
$this->addJoin('public.tbl_prestudent p', 'prestudent_id');
@@ -0,0 +1,83 @@
<?php
class Tabulator_preset_model extends DB_Model
{
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
$this->dbTable = 'public.tbl_tabulator_presets';
$this->pk = ['preset_id'];
$this->hasSequence = true;
$this->load->model('person/Benutzer_model', 'BenutzerModel');
}
public function getTabulatorPreset($presetId)
{
$query = "
SELECT *
FROM public.tbl_tabulator_presets
WHERE preset_id = ?
LIMIT 1
";
return $this->execQuery($query, array($presetId));
}
public function getTabulatorPresetsByUserAndTable($uid, $tableName)
{
$query = '
SELECT *
FROM public.tbl_tabulator_presets
WHERE benutzer_uid = ?
AND table_name = ?
ORDER BY preset_id ASC
';
return $this->execQuery($query, array($uid, $tableName));
}
public function createTabulatorPreset($uid, $tableName, $presetName, $preset)
{
$presetCreationResult = $this->insert([
"benutzer_uid" => $uid,
"table_name" => $tableName,
"preset_name" => $presetName,
"preset_json" => json_encode($preset),
]);
if (isError($presetCreationResult))
{
return error('Something went wrong during preset creation!');
}
return success($presetCreationResult->retval);
}
public function updateTabulatorPreset($presetId, $preset)
{
$presetUpdateResult = $this->update(["preset_id" => $presetId], ["preset_json" => json_encode($preset)]);
if (isError($presetUpdateResult))
{
return error('Something went wrong during preset update!');
}
return success($presetUpdateResult->retval);
}
public function deleteTabulatorPreset($presetId)
{
$presetDeletionReseult = $this->delete(["preset_id" => $presetId]);
if (isError($presetDeletionReseult))
{
return error('Something went wrong during preset deletion!');
}
return success($presetDeletionReseult->retval);
}
}
+2 -1
View File
@@ -56,12 +56,13 @@ $configArray = [
<router-view
default-semester="<?= $variables['semester_aktuell']; ?>"
active-addons="<?= defined('ACTIVE_ADDONS') ? ACTIVE_ADDONS : ''; ?>"
stv-root="<?= site_url('Studentenverwaltung'); ?>"
stv-root="<?= site_url('studvw'); ?>"
cis-root="<?= CIS_ROOT; ?>"
avatar-url="<?= site_url('Cis/Pub/bild/person/' . getAuthPersonId()); ?>"
logout-url="<?= site_url('Cis/Auth/logout'); ?>"
:permissions="<?= htmlspecialchars(json_encode($permissions)); ?>"
:config="<?= htmlspecialchars(json_encode($configArray)); ?>"
auth-uid="<?= getAuthUID()?>"
>
</router-view>
</div>
+74 -10
View File
@@ -30,6 +30,12 @@ if(defined('CIS4')){
}
?>
<?php
$config = $this->load->config('studierendenantrag');
$showInfoModalAbmeldung = $this->config->item('showInfoModalAbmeldung');
?>
<div id="wrapper">
<div class="fhc-header">
@@ -43,17 +49,75 @@ if(defined('CIS4')){
<h4><?= $array['bezeichnungStg']; ?> (<?= $array['bezeichnungOrgform']; ?>)</h4>
<?php foreach ($array['allowedNewTypes'] as $type) { ?>
<div class="alert alert-secondary">
<p><?= $this->p->t('studierendenantrag', 'calltoaction_' . $type); ?></p>
<hr>
<a
href="<?= site_url('lehre/Studierendenantrag/' . strtolower($type) . '/' . $prestudent_id); ?>"
class="btn btn-outline-secondary"
>
<i class="fa fa-plus"></i> <?= $this->p->t('studierendenantrag', 'antrag_typ_' . $type); ?>
</a>
<p><?= $this->p->t('studierendenantrag', 'calltoaction_' . $type); ?></p>
<hr>
<?php
if($type == 'Abmeldung' && $showInfoModalAbmeldung){
$href = "#modalinfo" . $prestudent_id;
}
else
$href = site_url('lehre/Studierendenantrag/' . strtolower($type) . '/' . $prestudent_id);
?>
<a
href="<?= $href ?>"
class="btn btn-outline-secondary"
<?php if($type == 'Abmeldung' && $showInfoModalAbmeldung): ?>
data-bs-toggle="modal"
<?php endif; ?>
>
<i class="fa fa-plus"></i> <?= $this->p->t('studierendenantrag', 'antrag_typ_' . $type); ?>
</a>
<!-- Modal -->
<div
class="modal fade"
id="modalinfo<?= $prestudent_id; ?>"
tabindex="-1"
aria-labelledby="modalinfoLabel<?= $prestudent_id; ?>"
aria-hidden="true"
>
<div class="modal-dialog modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5
class="modal-title"
id="modalinfoLabel<?= $prestudent_id; ?>"
> <?= $this->p->t('studierendenantrag', 'info_consultation'); ?>
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<?php
$hrefUnterbrechung = site_url('lehre/Studierendenantrag/unterbrechung/' . $prestudent_id);
?>
<div class="modal-body">
<div
class="form-control"
style="width: 100%; height: 250px;overflow:auto; white-space: normal;"
><?= $this->p->t('studierendenantrag', 'modalHinweistext_Abmeldung', ['linkUnterbrechung' => $hrefUnterbrechung] ); ?>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-primary" data-bs-dismiss="modal"><?= $this->p->t('ui', 'cancel'); ?></button>
<a
href="<?= site_url('lehre/Studierendenantrag/abmeldung/' . $prestudent_id); ?>"
class="btn btn-outline-primary"
> <?= $this->p->t('studierendenantrag', 'link_Abmeldung'); ?>
</a>
</div>
</div>
</div>
</div>
</div>
<?php } ?>
<?php } ?>
<table class="table">
<thead>
@@ -205,7 +269,7 @@ if(defined('CIS4')){
</a>
<lv-popup
id="modallv<?= $antrag->studierendenantrag_id; ?>"
antrag-id = "<?= $antrag->studierendenantrag_id; ?>"
:antrag-id = "<?= $antrag->studierendenantrag_id; ?>"
>
<?= $this->p->t('studierendenantrag', 'my_lvs'); ?>
</lv-popup>
@@ -154,6 +154,7 @@
generateJSsInclude('vendor/npm-asset/primevue/confirmdialog/confirmdialog.min.js');
generateJSsInclude('vendor/npm-asset/primevue/confirmationservice/confirmationservice.min.js');
generateJSsInclude('vendor/npm-asset/primevue/tieredmenu/tieredmenu.min.js');
generateJSsInclude('vendor/npm-asset/primevue/progressbar/progressbar.min.js');
}
if($vuedatepicker11) generateJSsInclude('vendor/vuejs/vuedatepicker_js11/vue-datepicker.iife.js');
+4 -4
View File
@@ -109,9 +109,9 @@
"type": "package",
"package": {
"name": "fortawesome/font-awesome6",
"version": "6.1.1",
"version": "6.7.2",
"dist": {
"url": "https://github.com/FortAwesome/Font-Awesome/archive/refs/tags/6.1.1.zip",
"url": "https://github.com/FortAwesome/Font-Awesome/archive/refs/tags/6.7.2.zip",
"type": "zip"
}
}
@@ -468,7 +468,7 @@
"fgelinas/timepicker": "0.3.3",
"fortawesome/font-awesome4": "4.7.*",
"fortawesome/font-awesome6": "6.1.*",
"fortawesome/font-awesome6": "6.7.*",
"fzaninotto/faker": "1.*",
"firebase/php-jwt": "^6.0",
@@ -496,7 +496,7 @@
"netcarver/textile": "3.7.*",
"nicolaskruchten/pivottable": "2.23.0",
"npm-asset/primevue": "3.29.1",
"npm-asset/primeicons": "5.0.0",
"npm-asset/primeicons": "6.0.1",
"olifolkerd/tabulator4": "4.9.*",
"olifolkerd/tabulator5": "5.5.*",
Generated
+5 -5
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "869cbc35bd1ba90ab90934fcb41b0f51",
"content-hash": "bc21de2a48b960781c1ed028c4b8d7b9",
"packages": [
{
"name": "afarkas/html5shiv",
@@ -960,10 +960,10 @@
},
{
"name": "fortawesome/font-awesome6",
"version": "6.1.1",
"version": "6.7.2",
"dist": {
"type": "zip",
"url": "https://github.com/FortAwesome/Font-Awesome/archive/refs/tags/6.1.1.zip"
"url": "https://github.com/FortAwesome/Font-Awesome/archive/refs/tags/6.7.2.zip"
},
"type": "library"
},
@@ -1565,10 +1565,10 @@
},
{
"name": "npm-asset/primeicons",
"version": "5.0.0",
"version": "6.0.1",
"dist": {
"type": "tar",
"url": "https://registry.npmjs.org/primeicons/-/primeicons-5.0.0.tgz"
"url": "https://registry.npmjs.org/primeicons/-/primeicons-6.0.1.tgz"
},
"type": "npm-asset",
"license": [
+9
View File
@@ -91,6 +91,15 @@
color: black;
}
.tag_skyblue {
background-color: #90D5FF;
color: black;
}
.tag_blush {
background-color: #A83E3E;
color: white;
}
.tag_done {
text-decoration: line-through;
}
+58
View File
@@ -0,0 +1,58 @@
/**
* 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 <https://www.gnu.org/licenses/>.
*/
export default {
getTabulatorPresets(tableName) {
return {
method: "get",
url:
"/api/frontend/v1/TabulatorPresets/getTabulatorPresets?tableName=" +
tableName,
};
},
createTabulatorPreset(tableName, presetName, preset) {
return {
method: "post",
url: "/api/frontend/v1/TabulatorPresets/createTabulatorPreset",
params: {
tableName,
presetName,
preset,
},
};
},
updateTabulatorPreset(presetId, preset) {
return {
method: "post",
url: "/api/frontend/v1/TabulatorPresets/updateTabulatorPreset",
params: {
presetId,
preset,
},
};
},
deleteTabulatorPreset(tabulatorPresetId) {
return {
method: "post",
url:
"/api/frontend/v1/TabulatorPresets/deleteTabulatorPreset",
params: {
presetId: tabulatorPresetId,
},
};
},
};
@@ -469,6 +469,7 @@ export default {
:tabulator-options="betriebsmittel_table_options"
tableOnly
:sideMenu="false"
:isUsingPresets="true"
/>
</div>
</div>
@@ -43,6 +43,7 @@ export default {
maxWidth: 40,
headerClick: this.collapseFunction,
visible: true,
responsive: 0,
},
{
title: "placeholder",
@@ -51,6 +52,7 @@ export default {
headerFilter: true,
minWidth: 200,
visible: true,
responsive: 0,
},
{
title: "placeholder",
@@ -59,6 +61,7 @@ export default {
headerFilter: true,
minWidth: 200,
visible: true,
responsive: 1,
},
{
title: "placeholder",
@@ -71,6 +74,7 @@ export default {
visible: true,
formatter: "datetime",
formatterParams: this.datetimeFormatterParams(),
responsive: 4,
},
{
title: "placeholder",
@@ -83,6 +87,7 @@ export default {
visible: true,
formatter: "datetime",
formatterParams: this.datetimeFormatterParams(),
responsive: 3,
},
{
title: "placeholder",
@@ -91,6 +96,7 @@ export default {
headerFilter: true,
minWidth: 200,
visible: true,
responsive: 2,
},
],
},
@@ -176,7 +176,6 @@ export const Profil = {
this.data = data.profil_data.data;
this.calendarSyncUrls = data.calendar_sync_urls ?? [];
this.authPermissions = data.permissions;
console.log(data.profil_data);
},
zustellAdressenCount() {
if (!this.data || !this.data.adressen) {
@@ -326,8 +326,10 @@ export default {
this.reload();
},
rebuildPrestudentTags(){
const id = [this.headerData[0].prestudent_id];
const params = {
id : this.headerData[0].prestudent_id,
ids: id,
typeId: 'prestudent_id',
sem: this.currentSemester
};
@@ -6,6 +6,7 @@ import BaseTreemenu from '../Base/Treemenu.js';
import StvStudiensemester from "../Stv/Studentenverwaltung/Studiensemester.js";
import LvTable from "./Setup/Table.js";
import LvTabs from "./Setup/Tabs.js";
import NavLanguage from "../navigation/Language.js";
import ApiDetails from "../../api/lehrveranstaltung/details.js";
import ApiLektor from "../../api/lehrveranstaltung/lektor.js";
@@ -25,7 +26,8 @@ export default {
StvStudiensemester,
LvTable,
LvTabs,
AppConfig
AppConfig,
NavLanguage
},
props: {
avatarUrl: String,
@@ -47,10 +49,13 @@ export default {
permissionLehrveranstaltung: this.permissions['lehre/lehrveranstaltung'],
permissionGruppenEntfernen: this.permissions['lv-plan/gruppenentfernen'],
permissionLektorEntfernen: this.permissions['lv-plan/lektorentfernen'],
language: Vue.computed(() => this.$p.user_language),
isMobile: false,
}
},
data() {
return {
sidebarCollapsed: false,
appconfig:{},
configEndpoints: ApiLvConfig,
selected: [],
@@ -25,18 +25,34 @@ export default {
return {
stdsem: '',
endpoint: ApiLVNoten,
tabulatorOptions: {
tabulatorOptionsZeugnis: {
visibleColumns: {
vorname: true,
nachname: true,
uid: true,
lehrveranstaltung_bezeichnung: false
},
headerFilter: {
vorname: true,
nachname: true,
uid: true,
lehrveranstaltung_bezeichnung: false
},
persistenceZeugnisID: 'lv-details-noten-zeugnis-2025120401',
},
tabulatorOptionsTeacher: {
visibleColumns: {
vorname: true,
nachname: true,
student_uid: true,
lehrveranstaltung_bezeichnung: false
},
headerFilter: {
vorname: true,
nachname: true,
student_uid: true,
lehrveranstaltung_bezeichnung: false
},
persistenceTeacherID: 'lv-details-noten-teacher-2025120401',
},
zeugnisLoaded: false,
@@ -98,7 +114,7 @@ export default {
:id="modelValue.lehrveranstaltung_id"
:all-semester="!!stdsem"
:endpoint="endpoint"
:optionalTabulatorOptions="tabulatorOptions"
:optionalTabulatorOptions="tabulatorOptionsZeugnis"
@loaded="onZeugnisLoaded"/>
</div>
<div class="col-4">
@@ -108,7 +124,7 @@ export default {
:all-semester="!!stdsem"
:endpoint="endpoint"
@copied="reload"
:optionalTabulatorOptions="tabulatorOptions"
:optionalTabulatorOptions="tabulatorOptionsTeacher"
@loaded="onTeacherLoaded"/>
</div>
</div>
@@ -54,7 +54,8 @@ export default {
cisRoot: String,
avatarUrl: String,
logoutUrl: String,
activeAddons: String // semicolon separated list of active addons
activeAddons: String, // semicolon separated list of active addons
authUid: String
},
provide() {
return {
@@ -88,7 +89,9 @@ export default {
hasZGVMasterPermission: this.permissions['student/editMakkZgv'],
hasZGVDoctorPermission: this.permissions['student/editDokZgv'],
hasBismeldenPermission: this.permissions['student/editBismelden'],
authUid: this.authUid,
language: Vue.computed(() => this.$p.user_language),
isMobile: false,
}
},
data() {
@@ -216,6 +219,28 @@ export default {
return extraItems;
},
appMenuBisItems() {
const extraItems = [];
if (this.studiengangKz !== undefined) {
const studiengang_kz = String(this.studiengangKz);
extraItems.push({
link: FHC_JS_DATA_STORAGE_OBJECT.app_root
+ 'vilesci/bis/studentenmeldung.php?'
+ 'stg_kz=' + studiengang_kz,
description: 'stv/bis_stud_meldung'
});
extraItems.push({
link: FHC_JS_DATA_STORAGE_OBJECT.app_root
+ 'system/checkStudenten.php?'
+ 'stg_kz=' + studiengang_kz,
description: 'stv/bis_stud_plausicheck'
});
}
return extraItems;
},
linkRt(){
return FHC_JS_DATA_STORAGE_OBJECT.app_root + '/vilesci/stammdaten/reihungstestverwaltung.php'
},
@@ -289,6 +314,11 @@ export default {
sidebarCollapsed(newVal) {
if(newVal) this.$refs.hSplit.collapseLeft()
else this.$refs.hSplit.showBoth()
},
studiengangKz(newVal, oldVal) {
if (newVal !== oldVal) {
this.$refs.stvList.clearSelection();
}
}
},
methods: {
@@ -401,6 +431,9 @@ export default {
showAlertNoGroupChosen(){
this.$fhcAlert.alertError(this.$p.t('ui', 'alert_chooseGroupSem'));
},
showAlertNoDegreeProgramChosen(){
this.$fhcAlert.alertError(this.$p.t('ui', 'alert_chooseDegreeProgram'));
},
showMenu(refName) {
bootstrap.Dropdown.getOrCreateInstance(this.$refs[refName]).show();
},
@@ -613,13 +646,13 @@ export default {
</li>
</ul>
</li>
<li :class="{ dropend: appMenuExtraItems.length }"
@mouseenter="appMenuExtraItems.length && showMenu('lvPlanungToggle')"
@mouseleave="appMenuExtraItems.length && hideMenu('lvPlanungToggle')"
<li :class="{ dropend: appMenuLvPlanungItems.length }"
@mouseenter="appMenuLvPlanungItems.length && showMenu('lvPlanungToggle')"
@mouseleave="appMenuLvPlanungItems.length && hideMenu('lvPlanungToggle')"
>
<a
ref="lvPlanungToggle"
v-if="appMenuExtraItems.length"
v-if="appMenuLvPlanungItems.length"
class="dropdown-toggle"
href="#"
role="button"
@@ -636,7 +669,7 @@ export default {
>
{{ $p.t('stv/lvplanung') }}
</a>
<ul v-if="appMenuExtraItems.length" class="dropdown-menu p-0">
<ul v-if="appMenuLvPlanungItems.length" class="dropdown-menu p-0">
<li
v-for="(item, key) in appMenuLvPlanungItems"
:key="key"
@@ -663,6 +696,42 @@ export default {
{{ $p.t('stv/studienverlauf') }}
</a>
</li>
<li :class="{ dropend: appMenuBisItems.length }"
@mouseenter="appMenuBisItems.length && showMenu('bisToggle')"
@mouseleave="appMenuBisItems.length && hideMenu('bisToggle')"
>
<a
ref="bisToggle"
v-if="appMenuBisItems.length"
class="dropdown-toggle"
href="#"
role="button"
data-bs-toggle="dropdown"
aria-expanded="false"
data-bs-popper-config='{"strategy":"fixed"}'
>
{{ $p.t('stv/bis_stud') }}
</a>
<a
v-else
href="#"
@click.prevent="showAlertNoDegreeProgramChosen"
>
{{ $p.t('stv/bis_stud') }}
</a>
<ul v-if="appMenuBisItems.length" class="dropdown-menu p-0">
<li
v-for="(item, key) in appMenuBisItems"
:key="key"
>
<a class="dropdown-item" :href="item.link" target="_blank">
{{ $p.t(item.description) }}
</a>
</li>
</ul>
</li>
</app-menu>
</div>
</aside>
@@ -402,7 +402,7 @@ export default {
name="foto"
@actionbutton-clicked="sendInfomail"
>
<img alt="No Image" :src="noImageSrc" class="w-100">
<img alt="No Image" :src="noImageSrc" />
</form-input>
<form-input
v-if="!config.hiddenFields.includes('anmerkung')"
@@ -1,6 +1,14 @@
import { splitMailsHelper } from "../../../../helpers/EmailHelpers.js"
import { splitMailsHelper } from "../../../../helpers/EmailHelpers.js";
import { splitMailLinks } from "../../../../helpers/EmailHelpers.js";
import FormInput from "../../../Form/Input.js";
import FormForm from "../../../Form/Form.js";
export default {
name: "Kontaktieren",
components: {
FormInput,
FormForm
},
computed: {
internMails() {
if (this.modelValue.mail_intern)
@@ -21,38 +29,150 @@ export default {
props: {
modelValue: Object
},
inject: {
authUid: {
from: 'authUid',
required: true
},
},
data(){
return {
mailLinks: [],
mailLinksClicked: [],
showMailDialog: false,
showDivKomp: false,
showDivKompLocalStorageId: 'studvw-contact-showDivKomp'
}
},
activated() {
const showDivCompLocalStorage = window.localStorage.getItem(this.showDivKompLocalStorageId);
this.showDivKomp = (showDivCompLocalStorage === 'true') ? true : false;
},
methods: {
internMail(event) {
if (this.internMails.length)
{
splitMailsHelper(this.internMails, event, null, null, this.$fhcAlert, this.$p)
splitMailsHelper(this.internMails, event, null, null, this.$fhcAlert, this.$p, this.authUid)
}
},
privateMail(event) {
if (this.privateMails.length)
{
splitMailsHelper(this.privateMails, event, null,null, this.$fhcAlert, this.$p)
splitMailsHelper(this.privateMails, event, null, null, this.$fhcAlert, this.$p, this.authUid)
}
},
createMailLinks(event, mailGroup){
let recipients = mailGroup == 'private' ? this.privateMails : this.internMails;
this.mailLinks = splitMailLinks(
recipients,
"",
event.ctrlKey || event.metaKey,
this.authUid
);
this.mailLinksClicked = [];
this.showMailDialog = true;
},
handleMailLink(link, index){
this.mailLinksClicked.push(index);
window.open(link, '_blank', 'noopener');
},
reset(){
this.showMailDialog = false;
},
onSwitchChange(){
window.localStorage.setItem(this.showDivKompLocalStorageId, this.showDivKomp);
}
},
template: `
<div>
<div id="elementID"></div>
<div class="row">
<div class="col-lg-2">
<button class="btn btn-primary mb-2" @click="internMail($event)" :title="$p.t('stv', 'bccEMail')">
<i class="fa-solid fa-mail"></i> {{$p.t('stv', 'internEMail')}}
</button>
<div class="core-kontaktieren">
<div id="elementID"></div>
<div class="d-flex justify-content-between align-items-start pb-3">
<a
v-if="showMailDialog"
class="btn btn-outline-secondary m-2"
@click.prevent="reset"
>
<span class="fa-solid fa-delete-left" :title="this.$p.t('ui','zurueck')" ></span>
&nbsp;
<span>{{ $p.t('ui','zurueck') }}</span>
</a>
<div v-else>&nbsp;</div>
<form-input
container-class="form-switch mb-0"
type="checkbox"
:label="$p.t('ui/kompatibilityMode_WebmailClient')"
v-model="showDivKomp"
@change="onSwitchChange"
>
</form-input>
</div>
<div v-if="!showDivKomp">
<div class="row">
<div class="col-lg-2">
<button class="btn btn-primary mb-2" @click="internMail($event)" :title="$p.t('stv', 'bccEMail')">
<i class="fa-solid fa-mail"></i> {{$p.t('stv', 'internEMail')}}
</button>
</div>
</div>
<div class="row">
<div class="col-lg-2">
<button class="btn btn-primary mb-2" @click="privateMail($event)" :title="$p.t('stv', 'bccEMail')">
<i class="fa-solid fa-mail"></i> {{$p.t('stv', 'privateEMail')}}
</button>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-2">
<button class="btn btn-primary mb-2" @click="privateMail($event)" :title="$p.t('stv', 'bccEMail')">
<i class="fa-solid fa-mail"></i> {{$p.t('stv', 'privateEMail')}}
</button>
<div v-if="showDivKomp">
<div v-if="!showMailDialog" class="row">
<div class="col-lg-2">
<button class="btn btn-secondary mb-2" @click="createMailLinks($event, 'intern')" :title="$p.t('stv', 'bccEMail')">
<i class="fa-solid fa-mail"></i> {{$p.t('stv', 'internEMail')}} ({{$p.t('ui', 'webmailclient')}})
</button>
</div>
</div>
<div v-if="!showMailDialog" class="row">
<div class="col-lg-2">
<button class="btn btn-secondary mb-2" @click="createMailLinks($event, 'private')" :title="$p.t('stv', 'bccEMail')">
<i class="fa-solid fa-mail"></i> {{$p.t('stv', 'privateEMail')}} ({{$p.t('ui', 'webmailclient')}})
</button>
</div>
</div>
<div v-if="showMailDialog" class="mt-3">
<div v-if="mailLinks.length > 1" class="mt-2">
<p>{{$p.t('stv', 'zuvieleEMails')}}</p>
<div
v-for="(link,index) in mailLinks"
:key="index"
class="mb-2">
<a
:href="link"
@click.prevent="handleMailLink(link, index)"
>
Email {{ index + 1 }}
</a>
<span v-show="mailLinksClicked.includes(index)">&nbsp;<i class="fa-solid fa-check text-success"></i></span>
</div>
</div>
<div v-else>
<a
:href="mailLinks[0]"
@click.prevent="handleMailLink(mailLinks[0], index)"
class="btn btn-primary m-2"
>
{{$p.t('ui', 'openInMailClient')}}
</a>
</div>
</div>
</div>
</div>`
};
</div>
`
};
@@ -31,6 +31,11 @@ export default {
teacherLoaded: false,
};
},
computed: {
gradeListLink() {
return this.$api.getUri('/person/gradelist/index/' + this.modelValue.uid);
}
},
methods: {
reload() {
this.$refs.zeugnis.$refs.table.reloadTable();
@@ -69,13 +74,21 @@ export default {
const savedPath = window.localStorage.getItem(LOCAL_STORAGE_ID);
this.stdsem = savedPath || '';
},
template: `
template: /* html */`
<div class="stv-details-noten d-flex flex-column overflow-hidden">
<div class="mb-3">
<select class="form-select" v-model="stdsem" @input="saveStdsem">
<option value="">{{ $p.t('ui/current_semester') }}</option>
<option value="true">{{ $p.t('ui/all_semester') }}</option>
</select>
<div class="d-flex justify-content-between my-2">
<div>
<select class="form-select" v-model="stdsem" @input="saveStdsem">
<option value="">{{ $p.t('ui/current_semester') }}</option>
<option value="true">{{ $p.t('ui/all_semester') }}</option>
</select>
</div>
<div>
<a :href="gradeListLink" target="_blank">
{{ $p.t('stv/grades_gradelist') }}
<i class="fa-solid fa-arrow-up-right-from-square"></i>
</a>
</div>
</div>
<div class="row">
<div class="col-8">
@@ -21,6 +21,7 @@ export default {
},
data() {
return {
renderTabulator: false,
tabulatorEvents: []
};
},
@@ -118,13 +119,13 @@ export default {
created() {
this.$p.loadCategory(['global', 'stv', 'lehre', 'profil'])
.then(() => {
if (this.$refs.table.tableBuilt)
this.$refs.table.tabulator.columnManager.setColumns(this.tabulatorOptions.columns);
this.renderTabulator = true
});
},
template: `
<div class="stv-details-noten-repeater d-flex flex-column">
<core-filter-cmpt
v-if="renderTabulator"
ref="table"
:title="$p.t('stv/grades_title_repeater')"
:tabulator-options="tabulatorOptions"
@@ -28,11 +28,28 @@ export default {
},
data() {
return {
renderTabulator: false,
setOptinalTabulatorOptionsVisibility: false,
tabulatorEvents: [
{
event: "dataProcessed",
handler: () => this.$emit("loaded"),
},
{
event: "tableBuilt",
handler: () => {
if(this.optionalTabulatorOptions?.visibleColumns
&& this.setOptinalTabulatorOptionsVisibility)
{
for(let key in this.optionalTabulatorOptions.visibleColumns) {
if(this.optionalTabulatorOptions.visibleColumns[key])
this.$refs.table.tabulator.showColumn(key);
else
this.$refs.table.tabulator.hideColumn(key);
}
}
}
}
]
};
},
@@ -86,6 +103,8 @@ export default {
{ field: 'studiensemester_kurzbz', title: this.$p.t('lehre/studiensemester'), visible: false },
{ field: 'note', title: this.$p.t('stv/grades_numericgrade'), visible: false },
{ field: 'student_uid', title: this.$p.t('profil/studentIn'), visible: false },
{ field: 'vorname', title: this.$p.t('person/vorname'), visible: false },
{ field: 'nachname', title: this.$p.t('person/nachname'), visible: false },
{ field: 'lehrveranstaltung_id', title: this.$p.t('lehre/lehrveranstaltung_id'), visible: false },
{ field: 'punkte', title: this.$p.t('stv/grades_points'), visible: false }
],
@@ -132,13 +151,19 @@ export default {
created() {
this.$p.loadCategory(['stv', 'lehre', 'profil'])
.then(() => {
if (this.$refs.table.tableBuilt)
this.$refs.table.tabulator.columnManager.setColumns(this.tabulatorOptions.columns);
this.renderTabulator = true;
});
if(this?.optionalTabulatorOptions)
{
const localStorageKey = 'tabulator-' + this.optionalTabulatorOptions.persistenceTeacherID + '-columns';
this.setOptinalTabulatorOptionsVisibility = (window.localStorage.getItem(localStorageKey) === null) ? true : false;
}
},
template: `
<div class="stv-details-noten-teacher d-flex flex-column">
<core-filter-cmpt
v-if="renderTabulator"
ref="table"
:title="$p.t('stv/grades_title_teacher')"
:tabulator-options="tabulatorOptions"
@@ -34,6 +34,8 @@ export default {
},
data() {
return {
renderTabulator: false,
setOptinalTabulatorOptionsVisibility: false,
tabulatorEvents: [
{
event: "dataLoaded",
@@ -53,6 +55,21 @@ export default {
{
event: "rowDeselected",
handler: row => row.getElement().style.zIndex = ''
},
{
event: "tableBuilt",
handler: () => {
if(this.optionalTabulatorOptions?.visibleColumns
&& this.setOptinalTabulatorOptionsVisibility)
{
for(let key in this.optionalTabulatorOptions.visibleColumns) {
if(this.optionalTabulatorOptions.visibleColumns[key])
this.$refs.table.tabulator.showColumn(key);
else
this.$refs.table.tabulator.hideColumn(key);
}
}
}
}
],
stdsem: '',
@@ -205,7 +222,10 @@ export default {
{ field: 'lehrform', title: this.$p.t('lehre/lehrform'), visible: false },
{ field: 'kurzbz', title: this.$p.t('lehre/kurzbz'), visible: false },
{ field: 'punkte', title: this.$p.t('stv/grades_points'), visible: false },
{ field: 'lehrveranstaltung_bezeichnung_english', title: this.$p.t('stv/grades_lehrveranstaltung_bezeichnung_english'), visible: false }
{ field: 'lehrveranstaltung_bezeichnung_english', title: this.$p.t('stv/grades_lehrveranstaltung_bezeichnung_english'), visible: false },
{ field: 'uid', title: this.$p.t('person/uid'), visible: false },
{ field: 'vorname', title: this.$p.t('person/vorname'), visible: false },
{ field: 'nachname', title: this.$p.t('person/nachname'), visible: false }
];
const hasDocuments = ['both', 'inline'].includes(this.config.documents);
@@ -215,6 +235,7 @@ export default {
columns.push({
field: 'actions',
title: this.$p.t('global/actions'),
minWidth: 120,
cssClass: "overflow-visible",
headerSort: false,
formatter: cell => {
@@ -308,13 +329,19 @@ export default {
created() {
this.$p.loadCategory(['global', 'stv', 'lehre', 'person'])
.then(() => {
if (this.$refs.table.tableBuilt)
this.$refs.table.tabulator.columnManager.setColumns(this.tabulatorOptions.columns);
this.renderTabulator = true;
});
if(this?.optionalTabulatorOptions)
{
const localStorageKey = 'tabulator-' + this.optionalTabulatorOptions.persistenceZeugnisID + '-columns';
this.setOptinalTabulatorOptionsVisibility = (window.localStorage.getItem(localStorageKey) === null) ? true : false;
}
},
template: `
<div class="stv-details-noten-zeugnis h-100 d-flex flex-column">
<core-filter-cmpt
v-if="renderTabulator"
ref="table"
:title="$p.t('stv/grades_title_zeugnis')"
:tabulator-options="tabulatorOptions"
@@ -11,6 +11,7 @@ import {
} from "../../../tabulator/filters/extendedHeaderFilter.js";
import { addTagInTable, deleteTagInTable, updateTagInTable } from "../../../../js/helpers/TagHelper.js";
import { tagFormatter } from "../../../../js/tabulator/formatter/tags.js";
import ModalLoading from "./List/ModalLoading.js";
import ApiTag from "../../../api/factory/stv/tag.js";
import ListFilter from './List/Filter.js';
@@ -30,7 +31,8 @@ export default {
CoreFilterCmpt,
ListNew,
CoreTag,
ListFilter
ListFilter,
ModalLoading
},
directives: {
draggable
@@ -218,10 +220,32 @@ export default {
oldScrollUrl: '',
oldScrollLeft: 0,
oldScrollTop: 0,
allPrestudents: []
allPrestudents: [],
rebuildData: [],
isLoading: false,
progress: -1,
total: -1,
processed: -1
}
},
computed: {
generalPresets: function() {
return [
{
"id": null,
"name": "Standard",
"displayedColumns": [
"uid","titelpre","tags","nachname","vorname","titelpost",
"ersatzkennzeichen","gebdatum","geschlecht",
"semester_berechnet","verband","gruppe","studiengang",
"matrikelnr","person_id","status","orgform_kurzbz",
"studienplan_bezeichnung","prestudent_id","priorisierung_relativ"
],
"headerFilters": [],
"sort": null
}
];
},
countsToHTML: function() {
return this.$p.t('global/ausgewaehlt')
+ ': <strong>' + (this.selectedcount || 0) + '</strong>'
@@ -508,7 +532,7 @@ export default {
e.preventDefault();
this.$refs.table.tabulator.deselectRow();
this.$refs.table.tabulator.selectRow();
this.$refs.table.tabulator.selectRow('active');
}
if (!this.focusObj)
@@ -658,6 +682,74 @@ export default {
this.oldScrollUrl = this.$refs.table.tabulator.getAjaxUrl();
}
},
async rebuild(selected){
const maxbulksize = 25;
const minbulksize = 5;
let bulksize = minbulksize;
this.isLoading = true;
let prestudentIds = [];
if(!selected.length)
prestudentIds = this.$refs.table.tabulator
.getData()
.map(row => row.prestudent_id);
else
prestudentIds = selected.map(item => item.prestudent_id);
this.progress = 0;
this.total = prestudentIds.length;
this.processed = 0;
this.rebuildData = [
[],
[],
[]
];
this.showModal();
while(this.processed < this.total)
{
let iteration_prestudentIds = prestudentIds.slice(
this.processed,
(this.processed + bulksize)
);
const params = {
ids: iteration_prestudentIds,
typeId: 'prestudent_id',
sem: this.studiensemesterKurzbz
};
try
{
const result = await this.$api.call(ApiTag.rebuildTagsforTypeId(params));
this.rebuildData[1] = [...this.rebuildData[1], ...result.data[1]];
this.rebuildData[2] = [...this.rebuildData[2], ...result.data[2]];
}
catch(error) {
this.$fhcAlert.handleSystemError(error);
};
this.processed += iteration_prestudentIds.length;
this.progress = Math.round(this.processed * 100 / this.total);
bulksize = bulksize * 2;
if(bulksize > maxbulksize) bulksize = maxbulksize;
}
if(this.rebuildData[1].length > 0)
{
this.$fhcAlert.alertSuccess(this.$p.t('tag', 'alertSuccessRebuild', { count: this.rebuildData[1].length }));
}
if(this.rebuildData[2].length > 0)
{
this.$fhcAlert.alertError(this.$p.t('tag', 'alertErrorRebuild') + this.rebuildData[2].toString());
}
this.isLoading = false;
this.$reloadList();
},
showModal(){
this.$refs.modalLocked.open();
},
},
created() {
if (this.tagsEnabled) {
@@ -690,8 +782,18 @@ export default {
// TODO(chris): filter component column chooser has no accessibilty features
template: /* html */`
<div class="stv-list h-100 pt-3">
<modal-loading
ref="modalLocked"
:isLoading="isLoading"
:progress="progress"
:total="total"
:processed="processed"
:message="$p.t('tag','messageModalWait')"
>
</modal-loading>
<div
class="tabulator-container d-flex flex-column h-100"
class="tabulator-container d-flex flex-column h-100 pe-2"
:class="{'has-filter': filter.length}"
tabindex="0"
@focusin="onFocus"
@@ -701,6 +803,9 @@ export default {
>
<core-filter-cmpt
ref="table"
:isUsingPresets="true"
presetsId="studVwListTable"
:generalPresets="generalPresets"
:description="countsToHTML"
:tabulator-options="tabulatorOptions"
:tabulator-events="tabulatorEvents"
@@ -730,7 +835,7 @@ export default {
-->
<template #additional>
<div class="pe-2">
<div class="pe-1">
<a :href="linkXLS" target="_blank">
<i class="fas fa-file-excel fa-xl text-success" :title="$p.t('stv', 'text_exportXLS', { count: selectedPrestudents.length })"></i>
</a>
@@ -748,6 +853,23 @@ export default {
zuordnung_typ="prestudent_id"
></core-tag>
<button
v-if="!selected.length"
class="btn btn-outline btn-light mb-1"
@click="rebuild(selected)"
:title="$p.t('tag','rebuild_tags') + ' Stg ' + currentSemester"
>
<i class="fa-solid fa-refresh pe-1"></i> STG
</button>
<button
v-else
class="btn btn-outline btn-light mb-1"
@click="rebuild(selected)"
:title="$p.t('tag','rebuild_tags') + ' ' + $p.t('ui','selection') + ' ' + currentSemester"
>
<i class="fa-solid fa-refresh pe-1"></i> {{selected.length}}
</button>
<template v-if="filter.length || headerFilterActive">
<div class="d-flex justify-content-center align-items-center gap-2 ps-4 position-absolute start-50 translate-middle-x">
<p class="text-danger mb-0">
@@ -0,0 +1,84 @@
import BsModal from "../../../Bootstrap/Modal.js";
import FormForm from "../../../Form/Form.js";
export default {
name: "modalLoading",
components: {
BsModal,
FormForm,
PvProgressBar: primevue.progressbar
},
props: {
isLoading: {
type: Boolean,
required: true
},
message: {
type: String,
required: true
},
progress: {
type: Number,
default: -1
},
total: {
type: Number,
default: -1
},
processed: {
type: Number,
default: -1
}
},
data(){
return{}
},
methods:{
open(){
this.$refs.modalLoading.show();
},
closeModal(){
this.$refs.modalLoading.hide();
}
},
template: `
<bs-modal
ref="modalLoading"
noCloseBtn
backdrop='static'
:keyboard='false'
dialog-class="modal-dialog-scrollable"
>
<template #title>
{{ $p.t('ui/loading') }}
</template>
<form-form
ref="formModal"
>
<div class="mt-3">
<div v-if="isLoading">
{{message}}
</div>
<div v-else>
{{$p.t('ui', 'fenster_schliessen')}}
</div>
</div>
</form-form>
<pv-progress-bar v-if="progress >= 0" class="my-3" :value="progress">{{processed}} / {{total}}</pv-progress-bar>
<template #footer>
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
<button
class="btn btn-primary"
:disabled="isLoading"
@click="closeModal()">
{{$p.t('ui', 'schliessen')}}
</button>
</div>
</template>
</bs-modal>
`,
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,551 @@
/**
* Copyright (C) 2026 fhcomplete.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import ApiTabulatorPresets from "../../../api/factory/tabulatorPresets.js";
/**
*
*/
export default {
name: "TablePresets",
props: {
presetsId: { type: String },
tabulator: { type: Object },
generalPresets: { type: Array, default: [] },
},
emits: ["tablePresetApplied"],
data: function () {
return {
customUserPresets: [],
presetInfo: null,
newPreset: null,
newPresetName: "",
presetIndexBeingApplied: null,
};
},
computed: {
allPresets() {
return this.$props.generalPresets.concat(this.customUserPresets);
},
presetInfoFormattedStrings() {
if (!this.presetInfo) return null;
const columnTitles = this.getColumnTitles();
let displayedColumns =
this.presetInfo.displayedColumns?.map(
(columnField) => columnTitles[columnField] ?? "",
) ?? [];
displayedColumns = displayedColumns.filter(
(string) => string.length,
);
const displayedColumnsString = displayedColumns.length
? this.$p.t("tabulator_presets/displayed_columns") +
" (" +
this.$p.t("tabulator_presets/in_order") +
"): " +
displayedColumns.join(", ")
: "";
let headerFilters = this.presetInfo.headerFilters
? Object.keys(this.presetInfo.headerFilters).map((column) =>
columnTitles[column]
? columnTitles[column] +
'["' +
this.presetInfo.headerFilters[column] +
'"]'
: "",
)
: [];
headerFilters = headerFilters.filter((string) => string.length);
const headerFiltersString = headerFilters.length
? this.$p.t("tabulator_presets/column_header_filters") +
": " +
headerFilters.join(", ")
: "";
const sortColumn = this.presetInfo.sort?.column
? columnTitles[this.presetInfo.sort.column]
: null;
const sortDirection =
this.presetInfo.sort?.direction === "desc"
? this.$p.t("tabulator_presets/desc")
: this.$p.t("tabulator_presets/asc");
const sortString = sortColumn
? this.$p.t("tabulator_presets/sort") +
": " +
sortColumn +
" (" +
sortDirection +
")"
: "";
return {
displayedColumns: displayedColumnsString,
headerFilters: headerFiltersString,
sort: sortString,
};
},
newPresetFormattedStrings() {
if (!this.newPreset) return null;
const columnTitles = this.getColumnTitles();
const displayedColumns = this.newPreset.displayedColumns.map(
(columnField) => columnTitles[columnField],
);
const displayedColumnsString = displayedColumns.length
? this.$p.t("tabulator_presets/displayed_columns") +
" (" +
this.$p.t("tabulator_presets/in_order") +
"): " +
displayedColumns.join(", ")
: "";
const headerFilters = Object.keys(this.newPreset.headerFilters).map(
(column) =>
columnTitles[column] +
'["' +
this.newPreset.headerFilters[column] +
'"]',
);
const headerFiltersString = headerFilters.length
? this.$p.t("tabulator_presets/column_header_filters") +
": " +
headerFilters.join(", ")
: "";
const sortColumn = this.newPreset.sort?.column
? columnTitles[this.newPreset.sort.column]
: null;
const sortDirection =
this.newPreset.sort?.direction === "desc"
? this.$p.t("tabulator_presets/desc")
: this.$p.t("tabulator_presets/asc");
const sortString = sortColumn
? this.$p.t("tabulator_presets/sort") +
": " +
sortColumn +
" (" +
sortDirection +
")"
: "";
return {
displayedColumns: displayedColumnsString,
headerFilters: headerFiltersString,
sort: sortString,
};
},
},
watch: {
allPresets: {
handler() {
this.hidePresetInfo();
this.hideNewPresetForm();
},
deep: true,
},
},
methods: {
getPresetIndex() {
return this.presetIndexBeingApplied;
},
async showPresetInfo(preset) {
let shouldUpdatePresetInfo;
if (!this.presetInfo) {
shouldUpdatePresetInfo = true;
} else if (preset.id) {
shouldUpdatePresetInfo = preset.id !== this.presetInfo.id;
} else {
shouldUpdatePresetInfo = preset.name !== this.presetInfo.name;
}
if (shouldUpdatePresetInfo) {
this.presetInfo = { ...preset };
}
this.hideNewPresetForm();
await this.$nextTick();
if (
!this.$refs.presetInfoCollapsible
.getAttribute("class")
.split(" ")
.includes("show")
) {
this.togglePresetInfoCollapsible();
}
},
async hidePresetInfo() {
if (
this.$refs.presetInfoCollapsible
.getAttribute("class")
.split(" ")
.includes("show")
) {
this.togglePresetInfoCollapsible();
}
await this.$nextTick();
this.presetInfo = null;
},
togglePresetInfoCollapsible() {
new bootstrap.Collapse(
"#presetInfoCollapsible_" + this.$props.presetsId,
{ toggle: true },
);
},
async showNewPresetForm() {
this.generateNewPreset();
this.hidePresetInfo();
await this.$nextTick();
if (
!this.$refs.newPresetFormCollapsible
.getAttribute("class")
.split(" ")
.includes("show")
) {
this.toggleNewPresetFormCollapsible();
}
},
generateNewPreset() {
const columns = this.tabulator.columnManager.columns.filter(
(column) => column.field !== "collapse",
);
const displayedColumns = columns
.filter((column) => column.visible)
.map((column) => column.field);
const unparsedHeaderFilters =
this.tabulator.modules.filter.headerFilters;
const headerFilterFieldValuePairs = Object.entries(
unparsedHeaderFilters,
).map(([columnField, filter]) => [columnField, filter.value]);
const headerFiltersFieldValuePairsExcludingHiddenColumns =
headerFilterFieldValuePairs.filter(
([columnField, filterValue]) => {
return displayedColumns.includes(columnField);
},
);
const headerFilters = Object.fromEntries(
headerFiltersFieldValuePairsExcludingHiddenColumns,
);
const activeSort = this.tabulator.getSorters()[0];
const sort = activeSort
? {
column: activeSort.field,
direction: activeSort.dir,
}
: null;
this.newPreset = {
displayedColumns,
headerFilters,
sort,
};
},
hideNewPresetForm() {
if (
this.$refs.newPresetFormCollapsible
.getAttribute("class")
.split(" ")
.includes("show")
) {
this.toggleNewPresetFormCollapsible();
}
},
toggleNewPresetFormCollapsible() {
this.newPresetName = "";
new bootstrap.Collapse(
"#newPresetFormCollapsible_" + this.$props.presetsId,
{ toggle: true },
);
},
async createPreset() {
if (!this.newPresetName?.length) return;
const presetCreationResponse = await this.$api.call(
ApiTabulatorPresets.createTabulatorPreset(
this.$props.presetsId,
this.newPresetName,
this.newPreset,
),
);
if (presetCreationResponse.meta.status === "success") {
this.fetchCustomUserTabulatorPresets();
this.hideNewPresetForm();
}
},
async deletePreset(preset) {
if (!preset.id) return;
if (
window.confirm(
this.$p
.t("tabulator_presets/preset_deletion_confirmation")
.replace("(((presetName)))", preset.name),
)
) {
const presetDeletionResponse = await this.$api.call(
ApiTabulatorPresets.deleteTabulatorPreset(preset.id),
);
if (presetDeletionResponse.meta.status === "success") {
this.fetchCustomUserTabulatorPresets();
}
}
},
syncPresetWithConfig(preset) {
const validColumns = this.$props.tabulator.getColumnDefinitions();
let isUpdateNecessary = false;
if (preset.displayedColumns) {
const initialNumberOfDisplayedColumns =
preset.displayedColumns.length;
preset.displayedColumns = preset.displayedColumns.filter(
(columnField) => {
return validColumns.some(
(column) => column.field === columnField,
);
},
);
const updatedNumberOfDisplayedColumns =
preset.displayedColumns.length;
isUpdateNecessary =
updatedNumberOfDisplayedColumns <
initialNumberOfDisplayedColumns;
}
if (preset.headerFilters) {
const initialNumberOfHeaderFilters = Object.keys(
preset.headerFilters,
).length;
Object.keys(preset.headerFilters).forEach((columnField) => {
if (
!validColumns.some(
(column) =>
column.field === columnField &&
column.headerFilter,
) ||
!preset.displayedColumns.includes(columnField)
) {
delete preset.headerFilters[columnField];
}
});
const updatedNumberOfHeaderFilters = Object.keys(
preset.headerFilters,
).length;
isUpdateNecessary =
isUpdateNecessary ||
updatedNumberOfHeaderFilters < initialNumberOfHeaderFilters;
}
if (preset.sort) {
if (
!validColumns.some(
(column) =>
column.field === preset.sort.column &&
column.headerSort,
)
) {
preset.sort = null;
isUpdateNecessary = true;
}
}
if (isUpdateNecessary) {
this.updatePreset(preset);
}
return preset;
},
async updatePreset(preset) {
if (!preset.id) return;
const tabulatorPresetUpdateResponse = await this.$api.call(
ApiTabulatorPresets.updateTabulatorPreset(preset.id, {
displayedColumns: preset.displayedColumns,
headerFilters: preset.headerFilters,
sort: preset.sort,
}),
);
if (tabulatorPresetUpdateResponse.meta.status === "success") {
this.fetchCustomUserTabulatorPresets();
}
},
async applyPreset(preset, index) {
this.presetIndexBeingApplied = index;
// workaround for updating DOM and displaying preset-loading spinner
// conventional use of $nextTick ineffective
setTimeout(() => {
preset = this.syncPresetWithConfig(preset);
window.localStorage["tabulatorPreset-" + this.$props.presetsId] =
preset.id ? "custom:" + preset.id : "general:" + preset.name;
this.$props.tabulator.modules.tablePresets.applyPreset(preset);
this.$emit("tablePresetApplied", { preset });
this.presetIndexBeingApplied = null;
}, 50);
},
async fetchCustomUserTabulatorPresets() {
let tabulatorPresetsResponse = await this.$api.call(
ApiTabulatorPresets.getTabulatorPresets(this.$props.presetsId),
);
if (tabulatorPresetsResponse.meta.status === "success") {
this.customUserPresets = tabulatorPresetsResponse.data.map(
(presetInfo) => {
return {
id: presetInfo.preset_id,
name: presetInfo.preset_name,
...JSON.parse(presetInfo.preset_json),
};
},
);
}
},
getColumnTitles() {
const columnLocalizations =
this.$props.tabulator.getLocale() !== "default"
? this.$props.tabulator.getLang().columns
: {};
const columnFieldTitlePairs = this.$props.tabulator
.getColumnDefinitions()
.map((column) => [
column.field,
columnLocalizations[column.field] ?? column.title,
]);
return Object.fromEntries(columnFieldTitlePairs);
},
applyStoredPreset() {
let storedPreset =
window.localStorage["tabulatorPreset-" + this.$props.presetsId];
if (!storedPreset) return;
let preset = null;
if (storedPreset.search("custom:") === 0) {
const customPresetId = parseInt(storedPreset.slice(7));
preset = this.customUserPresets.find(
(customPreset) => customPreset.id === customPresetId,
);
} else if (storedPreset.search("general:") === 0) {
const generalPresetName = storedPreset.slice(8);
preset = this.$props.generalPresets.find(
(generalPreset) => generalPreset.name === generalPresetName,
);
}
if (!preset) return;
this.applyPreset(preset, null);
},
},
async created() {
await this.$p.loadCategory(["tabulator_presets", "global"]);
await this.fetchCustomUserTabulatorPresets();
this.applyStoredPreset();
},
template: /*html*/ `
<div>
<div class="card">
<div class="card-header">{{ $p.t("tabulator_presets/table_presets") }}</div>
<div class="card-body d-flex flex-column">
<div class="d-flex flex-row gap-1 justify-content-start flex-wrap">
<div v-for="(preset, index) in allPresets" class="d-flex flex-column gap-1 mb-2">
<div @click="applyPreset(preset, index)" class="position-relative btn btn-dark py-1 px-2">
<span :class="{'opacity-0': getPresetIndex() === index}">{{ preset.name }}</span>
<div
v-if="getPresetIndex() === index"
class="position-absolute d-flex flex-row align-items-center justify-content-center"
style="inset:0;"
>
<div class="spinner-border spinner-border-sm" role="status" style>
<span class="visually-hidden">Loading...</span>
</div>
</div>
</div>
<div class="d-flex flex-row justify-content-center">
<div class="d-flex flex-row gap-2 px-3">
<span @click="showPresetInfo(preset)" type="button" class="fa-solid fa-circle-info"></span>
<span v-if="preset.id" @click="deletePreset(preset)" type="button" class="fa-solid fa-trash-can"></span>
</div>
</div>
</div>
<div>
<div @click="showNewPresetForm()" class="btn btn-dark py-1 px-2 d-flex flex-row align-items-center gap-1">
<span class="fa-solid fa-plus"></span>
<span class="flex-nowrap">{{ $p.t("tabulator_presets/save_current_config") }}</span>
</div>
</div>
</div>
<div :id="'presetInfoCollapsible_' + $props.presetsId" ref="presetInfoCollapsible" class="collapse">
<div class="d-flex flex-column">
<hr />
<div class="d-flex flex-column gap-2">
<div class="w-100 d-flex flex-row justify-content-between">
<span class="fw-bold">{{ presetInfo?.name }}</span>
<span @click="hidePresetInfo()" type="button" class="fa-solid fa-xmark"></span>
</div>
<span v-if="presetInfoFormattedStrings?.displayedColumns.length">{{ presetInfoFormattedStrings.displayedColumns }}</span>
<span v-if="presetInfoFormattedStrings?.headerFilters.length">{{ presetInfoFormattedStrings.headerFilters }}</span>
<span v-if="presetInfoFormattedStrings?.sort.length">{{ presetInfoFormattedStrings.sort }}</span>
</div>
</div>
</div>
<div :id="'newPresetFormCollapsible_' + $props.presetsId" ref="newPresetFormCollapsible" class="collapse">
<div class="d-flex flex-column">
<hr />
<div class="d-flex flex-column gap-2">
<div class="w-100 d-flex flex-row justify-content-between">
<span class="fw-bold">{{ $p.t("tabulator_presets/save_current_config") }}</span>
<span @click="hideNewPresetForm()" type="button" class="fa-solid fa-xmark"></span>
</div>
<span v-if="newPresetFormattedStrings?.displayedColumns.length">{{ newPresetFormattedStrings.displayedColumns }}</span>
<span v-if="newPresetFormattedStrings?.headerFilters.length">{{ newPresetFormattedStrings.headerFilters }}</span>
<span v-if="newPresetFormattedStrings?.sort.length">{{ newPresetFormattedStrings.sort }}</span>
<div class="d-flex flex-row justify-content-center align-items-center gap-2">
<input v-model="newPresetName" :placeholder="$p.t('tabulator_presets/preset_name')" />
<div
@click="createPreset()"
:class="{'opacity-50 pe-none': !newPresetName?.length}"
class="btn btn-dark py-1 px-2"
>
{{ $p.t('global/speichern') }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`,
};
+82 -3
View File
@@ -1,5 +1,6 @@
export async function splitMailsHelper(mails, event, subject, body, alertPluginRef, phrasenPluginRef) {
export async function splitMailsHelper(mails, event, subject, body, alertPluginRef, phrasenPluginRef, uidCC) {
await phrasenPluginRef.loadCategory('ui');
let splititem = ",";
let maillist = mails.join(splititem);
let useBcc = event?.ctrlKey || event?.metaKey;
@@ -46,7 +47,10 @@ export async function splitMailsHelper(mails, event, subject, body, alertPluginR
}
// construct the clean mailLink
let mailLink = useBcc ? `mailto:?bcc=${mailto}` : `mailto:${mailto}`;
let mailadressUid = (uidCC && typeof uidCC === 'string')
? uidCC + '@' + FHC_JS_DATA_STORAGE_OBJECT.domain
: 'noreply@' + FHC_JS_DATA_STORAGE_OBJECT.domain;
let mailLink = useBcc ? `mailto:${mailadressUid}?cc=${mailadressUid}&bcc=${mailto}` : `mailto:${mailto}`;
if (queryString) {
// If using BCC, the string already has a '?', so append with '&'. Otherwise, start with '?'
mailLink += useBcc ? `&${queryString}` : `?${queryString}`;
@@ -63,4 +67,79 @@ export async function splitMailsHelper(mails, event, subject, body, alertPluginR
}
}
}
}
}
/**
* just splits the list of mails
*
* @param mails emailadresses
* @param subject subject
* @param useBcc useBcc
* @returns array of links for splitted mails
*/
export function splitMailLinks(mails, subject = "", useBcc = false, uidCC= "", maxLength = 2024) {
if (!Array.isArray(mails) || mails.length === 0) {
return [];
}
const separator = ",";
const encodedSubject = subject ? encodeURIComponent(subject) : "";
// reserve space for the subject parameter
const subjectLength = encodedSubject
? `?subject=${encodedSubject}`.length
: 0;
const limit = maxLength - subjectLength;
const links = [];
let currentRecipients = [];
for (const mail of mails) {
if (!mail) continue;
const testRecipients = [...currentRecipients, mail];
const recipientString = testRecipients.join(separator);
if (recipientString.length > limit && currentRecipients.length > 0) {
links.push(createMailto(
currentRecipients.join(separator),
encodedSubject,
useBcc,
uidCC
));
currentRecipients = [mail];
}
else {
currentRecipients.push(mail);
}
}
if (currentRecipients.length > 0) {
links.push(createMailto(
currentRecipients.join(separator),
encodedSubject,
useBcc,
uidCC
));
}
return links;
}
function createMailto(recipients, encodedSubject, useBcc, uidCC) {
let mailadressUid = uidCC + '@technikum-wien.at';
let link = useBcc
? `mailto:${encodeURIComponent(mailadressUid)}?cc=${encodeURIComponent(mailadressUid)}&bcc=${encodeURIComponent(recipients)}`
: `mailto:${encodeURIComponent(recipients)}`;
if (encodedSubject) {
link += `${useBcc ? "&" : "?"}subject=${encodedSubject}`;
}
return link;
}
+10 -26
View File
@@ -6,18 +6,9 @@ export function addTagInTable(addedTag, rows, matchKey, tagsKey = "tags")
rows.forEach(row =>
{
const rowData = row.getData();
//add save check if String or Array and avoid same reference later for mutations
let raw = rowData[tagsKey];
let tags = typeof raw === "string"
? JSON.parse(raw || "[]")
: Array.isArray(raw)
? [...raw]
: [];
let updated = false;
for (const tag of addedTag.response)
addedTag.response.forEach(tag =>
{
if (rowData[matchKey] !== tag[matchKey])
return;
@@ -52,16 +43,10 @@ export function addTagInTable(addedTag, rows, matchKey, tagsKey = "tags")
});
rowData[tagsKey] = JSON.stringify(tags);
updated = true;
}
});
if (updated)
{
row.update({
[tagsKey]: JSON.stringify(tags)
});
row.reformat();
}
row.update(rowData);
});
}
@@ -75,10 +60,8 @@ export function deleteTagInTable(deletedTag, rows, tagsKeys = ['tags'])
let updates = {};
let changed = false;
for (const key of tagsKeys) {
let raw = rowData[key];
let tags = [];
tagsKeys.forEach(key => {
let tags;
try {
let initialTags = rowData[key] || "[]";
@@ -92,15 +75,15 @@ export function deleteTagInTable(deletedTag, rows, tagsKeys = ['tags'])
}
if (!Array.isArray(tags))
continue;
return;
let filtered = tags.filter(tag => tag?.id !== deletedTag);
if (filtered.length !== tags.length) {
if (filtered.length !== tags.length)
{
updates[key] = JSON.stringify(filtered);
changed = true;
}
}
});
if (changed) {
row.update(updates);
@@ -109,6 +92,7 @@ export function deleteTagInTable(deletedTag, rows, tagsKeys = ['tags'])
});
}
export function updateTagInTable(updatedTag, rows, fields = ['tags'])
{
if (!Array.isArray(fields))
@@ -9,9 +9,13 @@ export default class ResponsiveLayoutExtensionModule extends ResponsiveLayout {
}
generateCollapsedRowData(row) {
let result = super.generateCollapsedRowData(row)
const isLocalizationEnabled = this.table.options.locale && this.table.options.locale !== "default";
const columnHeadingTranslations = this.table?.getLang()?.columns;
let result = super.generateCollapsedRowData(row);
const isLocalizationEnabled =
this.table.options.locale &&
this.table.options.locale !== "default";
const columnHeadingTranslations = this.table?.initialized
? this.table.getLang()?.columns
: null;
if (isLocalizationEnabled && columnHeadingTranslations) {
result = result.map((column) => {
@@ -0,0 +1,64 @@
import Module from "../../../../vendor/olifolkerd/tabulator5/src/js/core/Module.js";
export default class TablePresetsModule extends Module {
static moduleName = "tablePresets";
static moduleInitNumber = 1;
constructor(table) {
super(table);
}
applyPreset(preset) {
let columnsConfig = this.table.options.columns;
let reorderedColumnsConfig = [];
preset.displayedColumns.forEach((columnField) => {
let columnConfig = columnsConfig.find(
(column) => column.field === columnField,
);
if (!columnConfig) return;
columnConfig.visible = true;
reorderedColumnsConfig.push(columnConfig);
});
let hiddenColumnsConfig = columnsConfig
.filter((column) => !preset.displayedColumns.includes(column.field))
.map((column) => {
column.visible = false;
return column;
});
this.table.setColumns(
reorderedColumnsConfig.concat(hiddenColumnsConfig),
);
this.table.clearHeaderFilter();
if (preset.headerFilters) {
Object.entries(preset.headerFilters).forEach(
([columnField, filterValue]) => {
this.table.setHeaderFilterValue(columnField, filterValue);
// double checking if filter inputs are filled in due to issues with custom header filters
const headerFilterInputElement =
this.table.columnManager.columns
.find((column) => column.field === columnField)
?.element.querySelector(
".tabulator-header-filter input",
);
if (
headerFilterInputElement &&
headerFilterInputElement.value !== filterValue
) {
headerFilterInputElement.value = filterValue;
}
},
);
}
this.table.clearSort();
if (preset.sort?.column) {
this.table.setSort(
preset.sort.column,
preset.sort.direction ?? "asc",
);
}
}
}
+2
View File
@@ -99,6 +99,7 @@ require_once('dbupdate_3.4/68957_dashboard_bookmark_neue_Spalte_sort.php');
require_once('dbupdate_3.4/68530_Dashboard_Cleanup.php');
require_once('dbupdate_3.4/75959_StudVw_Automatische_Tags.php');
require_once('dbupdate_3.4/76160_lvv_favorites.php');
require_once('dbupdate_3.4/77080_tabulator_presets_table.php');
// *** Pruefung und hinzufuegen der neuen Attribute und Tabellen
echo '<H2>Pruefe Tabellen und Attribute!</H2>';
@@ -396,6 +397,7 @@ $tabellen=array(
"public.tbl_studiengangstyp" => array("typ","bezeichnung","beschreibung","bezeichnung_mehrsprachig"),
"public.tbl_studienjahr" => array("studienjahr_kurzbz","bezeichnung"),
"public.tbl_studiensemester" => array("studiensemester_kurzbz","bezeichnung","start","ende","studienjahr_kurzbz","ext_id","beschreibung","onlinebewerbung"),
"public.tbl_tabulator_presets" => array("preset_id", "benutzer_uid", "table_name", "preset_json"),
"public.tbl_tag" => array("tag"),
"public.tbl_variable" => array("name","uid","wert"),
"public.tbl_variablenname" => array("name","defaultwert"),
@@ -16,7 +16,7 @@ if(!@$db->db_query("SELECT taglib FROM public.tbl_notiz_typ LIMIT 1"))
$tags = [
[
'typ_kurzbz' => 'wh_auto',
'bezeichnung' => '{WH, R}',
'bezeichnung' => '{WH, WH}',
'style' => 'tag_dark_grey',
'prioritaet' => 11,
'taglib' => 'tags/CoreWiederholerTagLib'
@@ -30,14 +30,14 @@ $tags = [
],
[
'typ_kurzbz' => 'out_auto',
'bezeichnung' => '{Out, Out}',
'bezeichnung' => '{Outgoing, Outgoing}',
'style' => 'tag_limette',
'prioritaet' => 12,
'taglib' => 'tags/CoreOutgoingTagLib'
],
[
'typ_kurzbz' => 'prewh_auto',
'bezeichnung' => '{Pre-WH, pre-R}',
'bezeichnung' => '{Pre-WH, pre-WH}',
'style' => 'tag_light_grey',
'prioritaet' => 13,
'taglib' => 'tags/CorePrewiederholerTagLib'
@@ -51,7 +51,7 @@ $tags = [
],
[
'typ_kurzbz' => 'unterbrecher_auto',
'bezeichnung' => '{UB, I}',
'bezeichnung' => '{UB, UB}',
'style' => 'tag_turquoise',
'prioritaet' => 15,
'taglib' => 'tags/CoreUnterbrecherTagLib'
@@ -70,6 +70,20 @@ $tags = [
'prioritaet' => 17,
'taglib' => 'tags/CoreJgvTagLib'
],
[
'typ_kurzbz' => 'preabbr_auto',
'bezeichnung' => '{Pre-Abbr, pre-Abbr}',
'style' => 'tag_blush',
'prioritaet' => 18,
'taglib' => 'tags/CorePreabbrecherTagLib'
],
[
'typ_kurzbz' => 'in_auto',
'bezeichnung' => '{Incoming, Incoming}',
'style' => 'tag_skyblue',
'prioritaet' => 19,
'taglib' => 'tags/CoreIncomingTagLib'
],
];
@@ -0,0 +1,55 @@
<?php
if (!defined('DB_NAME'))
exit('No direct script access allowed');
if (!$result = @$db->db_query("SELECT 1 FROM public.tbl_tabulator_presets LIMIT 1")) {
$qry = "CREATE TABLE public.tbl_tabulator_presets (
preset_id int NOT NULL,
benutzer_uid varchar(33) NOT NULL,
table_name varchar(255) NOT NULL,
preset_name varchar(255) NOT NULL,
preset_json varchar(10000) NOT NULL,
CONSTRAINT tbl_tabulator_presets_preset_id_pk PRIMARY KEY(preset_id),
CONSTRAINT tbl_tabulator_presets_benutzer_uid_fk FOREIGN KEY (benutzer_uid) REFERENCES public.tbl_benutzer(uid)
);";
if (!$db->db_query($qry))
echo '<strong>public.tbl_tabulator_presets: ' . $db->db_last_error() . '</strong><br>';
else
echo '<br>public.tbl_tabulator_presets: table created';
$db->db_query('CREATE SEQUENCE IF NOT EXISTS public.seq_tbl_tabulator_presets_preset_id
INCREMENT BY 1
NO MAXVALUE
NO MINVALUE
CACHE 1;');
$db->db_query("ALTER TABLE public.tbl_tabulator_presets ALTER COLUMN preset_id SET DEFAULT nextval('public.seq_tbl_tabulator_presets_preset_id');");
$qry = 'GRANT SELECT, UPDATE, INSERT, DELETE ON TABLE public.tbl_tabulator_presets TO web;';
if (!$db->db_query($qry))
echo '<strong>public.tbl_tabulator_presets: ' . $db->db_last_error() . '</strong><br>';
else
echo '<br>Granted privileges to <strong>web</strong> on public.tbl_tabulator_presets';
$qry = 'GRANT SELECT, UPDATE, INSERT, DELETE ON TABLE public.tbl_tabulator_presets TO vilesci;';
if (!$db->db_query($qry))
echo '<strong>public.tbl_tabulator_presets: ' . $db->db_last_error() . '</strong><br>';
else
echo '<br>Granted privileges to <strong>vilesci</strong> on public.tbl_tabulator_presets';
$qry = 'GRANT USAGE ON public.seq_tbl_tabulator_presets_preset_id TO web;';
if (!$db->db_query($qry))
echo '<strong>public.tbl_tabulator_presets: ' . $db->db_last_error() . '</strong><br>';
else
echo '<br>Granted privileges to <strong>web</strong> on public.tbl_tabulator_presets';
$qry = 'GRANT USAGE ON public.seq_tbl_tabulator_presets_preset_id TO vilesci;';
if (!$db->db_query($qry))
echo '<strong>public.tbl_tabulator_presets: ' . $db->db_last_error() . '</strong><br>';
else
echo '<br>Granted privileges to <strong>vilesci</strong> on public.tbl_tabulator_presets';
}
+753 -1
View File
@@ -41556,6 +41556,26 @@ array(
)
),
// grades
array(
'app' => 'core',
'category' => 'stv',
'phrase' => 'grades_gradelist',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Studienverlauf',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Grade list',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'stv',
@@ -49804,7 +49824,7 @@ array(
),
array(
'app' => 'core',
'category' => 'stv',
'category' => 'ui',
'phrase' => 'weitereEMail',
'insertvon' => 'system',
'phrases' => array(
@@ -61599,6 +61619,738 @@ I have been informed that I am under no obligation to consent to the transmissio
)
)
),
//Start Finetuning StudVW
array(
'app' => 'core',
'category' => 'ui',
'phrase' => 'fenster_schliessen',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Sie können nun das Fenster schließen',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'This window can be closed now',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tag',
'phrase' => 'rebuild_tags',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Neuberechnen Tags',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Rebuild Tags',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'ui',
'phrase' => 'selection',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Auswahl',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'selection',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tag',
'phrase' => 'messageModalWait',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Bitte warten Sie, bis alle Tags neu berechnet wurden!',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Please wait until all tags have been recalculated!',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tag',
'phrase' => 'alertSuccessRebuild',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Tags erfolgreich neuberechnet ({count} Prestudent*innnen)',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Tags successfully recalculated ({count} prestudents)',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tag',
'phrase' => 'alertErrorRebuild',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Fehler bei Neuberechnen Tags, PrestudentId(s): ',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Error during Recalculating tags, PrestudentId(s): ',
'description' => '',
'insertvon' => 'system'
)
)
),
//End Finetuning StudVW
// tabulator presets section phrases START
array(
'app' => 'core',
'category' => 'tabulator_presets',
'phrase' => 'displayed_columns',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Angezeigte Spalten',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Displayed columns',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator_presets',
'phrase' => 'in_order',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'in Reihenfolge',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'in order',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator_presets',
'phrase' => 'column_header_filters',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Spalten Header Filter',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Column header filters',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator_presets',
'phrase' => 'asc',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'aufsteigend',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'ascending',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator_presets',
'phrase' => 'desc',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'absteigend',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'descending',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator_presets',
'phrase' => 'sort',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Sortierung',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Sort',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator_presets',
'phrase' => 'preset_deletion_confirmation',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Sind Sie sicher, dass Sie die Voreinstellung (((presetName))) löschen möchten?',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Are you sure you want to delete preset (((presetName)))?',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator_presets',
'phrase' => 'table_presets',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Tabelle Voreinstellungen',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Table presets',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator_presets',
'phrase' => 'save_current_config',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Aktuelle Einstellung speichern',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Save current configuration',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator_presets',
'phrase' => 'preset_name',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Voreinstellung Name',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Preset name',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator_presets',
'phrase' => 'max_presets_count_err_msg',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Maximale Anzahl an Voreinstellungen erreicht!',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Maximum number of presets reached!',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator_presets',
'phrase' => 'preset_name_duplicate_err_msg',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Doppelte Voreinstellunge Namen sind nicht zulässig!',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Preset name duplicate not allowed!',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator_presets',
'phrase' => 'preset_not_found_err_msg',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Voreinstellung nicht gefunden!',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Preset not found!',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator_presets',
'phrase' => 'preset_not_own_update_err_msg',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Sie dürfen diese Voreinstellung nicht ändern!',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'You are not allowed to update this preset!',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator_presets',
'phrase' => 'preset_not_own_delete_err_msg',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Sie dürfen diese Voreinstellung nicht löschen!',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'You are not allowed to delete this preset!',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator_presets',
'phrase' => 'preset_creation_generic_err_msg',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Bei der Erstellung der Voreinstellung ist ein Fehler aufgetreten!',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Something went wrong during preset creation!',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator_presets',
'phrase' => 'preset_update_generic_err_msg',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Bei der Voreinstellungsänderung ist ein Fehler aufgetreten!',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Something went wrong during preset update!',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator_presets',
'phrase' => 'preset_deletion_generic_err_msg',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Beim Löschen der Voreinstellung ist ein Fehler aufgetreten!',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Something went wrong during preset deletion!',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator_presets',
'phrase' => 'invalid_params_err_msg',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Ungültige Parameterübergabe!',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Invalid parameters!',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'tabulator_presets',
'phrase' => 'modified',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'modifiziert',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'modified',
'description' => '',
'insertvon' => 'system'
)
)
),
// tabulator presets section phrases END
array(
'app' => 'core',
'category' => 'ui',
'phrase' => 'openInMailClient',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'In Mailanwendung öffnen',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Open in Mailclient',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'ui',
'phrase' => 'kompatibilityMode_WebmailClient',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Kompatibilitätsmodus für Webmail',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Open in Compatibility mode (Webmail)',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'ui',
'phrase' => 'webmailclient',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Webmail',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'webmail',
'description' => '',
'insertvon' => 'system'
)
)
),
// StudVw legacy Bis Links begin
array(
'app' => 'core',
'category' => 'stv',
'phrase' => 'bis_stud',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'BIS (Studierende)',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'BIS (Students)',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'stv',
'phrase' => 'bis_stud_meldung',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Meldung generieren (Studierende)',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Generate Report (Students)',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'stv',
'phrase' => 'bis_stud_plausicheck',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Plausibilitätsprüfung (Studierende)',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Plausibility Check (Students)',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'ui',
'phrase' => 'alert_chooseDegreeProgram',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Bitte einen Studiengang auswählen!',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Please select a degree program!',
'description' => '',
'insertvon' => 'system'
)
)
),
// StudVw legacy Bis Links ende
array(
'app' => 'core',
'category' => 'studierendenantrag',
'phrase' => 'link_Abmeldung',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Weiter zur Abmeldung',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Continue to De-registration',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'studierendenantrag',
'phrase' => 'modalHinweistext_Abmeldung',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Bevor du deine Abmeldung abschließt, möchten wir dir gerne ein Gespräch anbieten. Vielleicht gibt es Fragen, Unsicherheiten oder Herausforderungen, bei denen wir dich unterstützen können.<br>
<br>Du kannst dich jederzeit unverbindlich an deine Studiengangsassistenz, Studiengangsleitung oder das <a href='https://cis.technikum-wien.at/cis.php/CisVue/Cms/content/9248' target='_blank'>Student Support Team</a> wenden. Ein Gespräch ist natürlich freiwillig – manchmal hilft es aber, gemeinsam Optionen zu besprechen, zum Beispiel auch eine vorübergehende
<a href={linkUnterbrechung} target='_blank'>Studienunterbrechung</a>.
<br><br>Wenn du deine Abmeldung fortsetzen möchtest, kannst du das selbstverständlich tun.",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => "Before you finalize your deregistration, we would like to offer you a consultation. Perhaps you have questions, uncertainties, or challenges that we can help you with. <br>
<br>You can contact your program assistant, program director, or the <a href='https://cis.technikum-wien.at/cis.php/CisVue/Cms/content/9248' target='_blank'>Student Support Team</a> at any time without obligation. A consultation is, of course, voluntary; however, sometimes it helps to discuss options together, such as a
<a href={linkUnterbrechung} target='_blank'>temporary break</a> from your studies.
<br><br>If you wish to proceed with your deregistration, you can, of course, do so.",
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'studierendenantrag',
'phrase' => 'info_consultation',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Hinweis Beratungsgespräch',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Note Consultation',
'description' => '',
'insertvon' => 'system'
)
)
),
);