Compare commits

..
39 changed files with 1272 additions and 259 deletions
@@ -32,9 +32,9 @@ class Studentenverwaltung extends Auth_Controller
'student/keine_studstatuspruefung' => $this->permissionlib->isBerechtigt('student/keine_studstatuspruefung'),
'lehre/reihungstestAufsicht' => $this->permissionlib->isBerechtigt('lehre/reihungstestAufsicht'),
'system/change_outputformat' => $this->permissionlib->getOE_isEntitledFor('system/change_outputformat'),
'student/editBakkZgv' => $this->permissionlib->getSTG_isEntitledFor('student/editBakkZgv') ?: array(),
'student/editMakkZgv' => $this->permissionlib->getSTG_isEntitledFor('student/editMakkZgv') ?: array(),
'student/editDokZgv' => $this->permissionlib->getSTG_isEntitledFor('student/editDokZgv') ?: array(),
'student/editBakkZgv' => $this->permissionlib->isBerechtigt('student/editBakkZgv'),
'student/editMakkZgv' => $this->permissionlib->isBerechtigt('student/editMakkZgv'),
'student/editDokZgv' => $this->permissionlib->isBerechtigt('student/editDokZgv'),
'student/editBismelden' => $this->permissionlib->isBerechtigt('student/editBismelden')
],
'variables' => [
@@ -29,7 +29,7 @@ class Studium extends FHCAPI_Controller
public function __construct()
{
parent::__construct([
'getStudienAllSemester'=> self::PERM_LOGGED,
'getAllStudienSemester'=> self::PERM_LOGGED,
'getStudiengaengeForStudienSemester'=> self::PERM_LOGGED,
'getStudienplaeneBySemester'=> self::PERM_LOGGED,
'getLvEvaluierungInfo'=> self::PERM_LOGGED,
@@ -51,7 +51,7 @@ class Studium extends FHCAPI_Controller
//------------------------------------------------------------------------------------------------------------------
// Public methods
public function getStudienAllSemester(){
public function getAllStudienSemester(){
$parameter_studiensemester = $this->input->get('studiensemester',true);
$parameter_studiengang = $this->input->get('studiengang',true);
@@ -59,24 +59,26 @@ class Studium extends FHCAPI_Controller
$parameter_studienplan = $this->input->get('studienplan',true);
$aktuelles_studiensemester = current($this->getDataOrTerminateWithError($this->StudiensemesterModel->getAktOrNextSemester()));
if($this->getDataOrTerminateWithError($this->StudentModel->isStudent(getAuthUID()))){
$studentLehrverband =$this->StudentlehrverbandModel->loadWhere(["student_uid" => getAuthUID(), "studiensemester_kurzbz" => $aktuelles_studiensemester->studiensemester_kurzbz]);
$studentLehrverband = current($this->getDataOrTerminateWithError($studentLehrverband));
$lv_result =$this->StudentlehrverbandModel->loadWhere([
"student_uid" => getAuthUID(),
"studiensemester_kurzbz" => $aktuelles_studiensemester->studiensemester_kurzbz
]);
$lv_data = $this->getDataOrTerminateWithError($lv_result);
if ($studentLehrverband = current($lv_data)) {
$student_studiensemester = $studentLehrverband->studiensemester_kurzbz;
$student_studiengang = $studentLehrverband->studiengang_kz;
$student_semester = $studentLehrverband->semester;
}
$student_studiensemester = $studentLehrverband->studiensemester_kurzbz;
$student_studiengang = $studentLehrverband->studiengang_kz;
$student_semester = $studentLehrverband->semester;
$student_studienplan = $this->getStudienPlanFromPrestudentStatus(getAuthPersonId())->studienplan_id;
if(!isset($parameter_studiensemester))
$parameter_studiensemester = $student_studiensemester;
if(!isset($parameter_studiengang))
$parameter_studiengang = $student_studiengang;
if(!isset($parameter_semester))
$parameter_semester = $student_semester;
if(!isset($parameter_studienplan))
$parameter_studienplan = $student_studienplan;
$parameter_studiensemester = $parameter_studiensemester ?? $student_studiensemester;
$parameter_studiengang = $parameter_studiengang ?? $student_studiengang;
$parameter_semester = $parameter_semester ?? $student_semester;
$parameter_studienplan = $parameter_studienplan ?? $student_studienplan;
}
if(isset($parameter_studiensemester)){
@@ -239,7 +239,7 @@ class Konto extends FHCAPI_Controller
$data[$field] = $this->input->post($field);
if (defined('FAS_BUCHUNGSTYP_FIXE_KOSTENSTELLE') && isset(unserialize(FAS_BUCHUNGSTYP_FIXE_KOSTENSTELLE)[$data['buchungstyp_kurzbz']])) {
$data['studiengang_kz'] = unserialize(FAS_BUCHUNGSTYP_FIXE_KOSTENSTELLE)[$data['buchungstyp_kurzbz']];
$data['kostenstelle'] = unserialize(FAS_BUCHUNGSTYP_FIXE_KOSTENSTELLE)[$data['buchungstyp_kurzbz']];
}
$result = [];
@@ -43,7 +43,7 @@ class Prestudent extends FHCAPI_Controller
// Load language phrases
$this->loadPhrases([
'ui', 'studierendenantrag', 'lehre', 'global'
'ui', 'studierendenantrag', 'lehre'
]);
}
@@ -98,9 +98,11 @@ class Prestudent extends FHCAPI_Controller
'person_id',
'berufstaetigkeit_code',
'ausbildungcode',
'zgv_code',
'zgvort',
'zgvdatum',
'zgvnation',
'zgvmas_code',
'zgvmaort',
'zgvmadatum',
'zgvmanation',
@@ -108,6 +110,7 @@ class Prestudent extends FHCAPI_Controller
'bismelden',
'anmerkung',
'dual',
'zgvdoktor_code',
'zgvdoktorort',
'zgvdoktordatum',
'zgvdoktornation',
@@ -122,57 +125,6 @@ class Prestudent extends FHCAPI_Controller
'standort_code'
];
// add zgv code fields only if user has permission
$this->load->library('PermissionLib');
$prestudentres = $this->PrestudentModel->load($prestudent_id);
if(!hasData($prestudentres))
{
$this->terminateWithError($this->p->t('ui', 'error_fieldNotFound', ['field' => 'Prestudent ' . $prestudent_id]));
}
$prestudent = (getData($prestudentres))[0];
$bakkZgvStg = $this->permissionlib->getSTG_isEntitledFor('student/editBakkZgv') ?: array();
$makkZgvStg = $this->permissionlib->getSTG_isEntitledFor('student/editMakkZgv') ?: array();
$dokZgvStg = $this->permissionlib->getSTG_isEntitledFor('student/editDokZgv') ?: array();
if(in_array($prestudent->studiengang_kz, $bakkZgvStg))
{
$array_allowed_props_prestudent[] = 'zgv_code';
}
else if(!is_null($this->input->post('zgv_code')))
{
$this->terminateWithError(
$this->p->t('global', 'zgv')
. ' - ' .
$this->p->t('ui', 'error_keineBerechtigungStg')
);
}
if(in_array($prestudent->studiengang_kz, $makkZgvStg))
{
$array_allowed_props_prestudent[] = 'zgvmas_code';
}
else if(!is_null($this->input->post('zgvmas_code')))
{
$this->terminateWithError(
$this->p->t('lehre', 'zgvMaster')
. ' - ' .
$this->p->t('ui', 'error_keineBerechtigungStg')
);
}
if(in_array($prestudent->studiengang_kz, $dokZgvStg))
{
$array_allowed_props_prestudent[] = 'zgvdoktor_code';
}
else if(!is_null($this->input->post('zgvdoktor_code')))
{
$this->terminateWithError(
$this->p->t('lehre', 'zgvDoktor')
. ' - ' .
$this->p->t('ui', 'error_keineBerechtigungStg')
);
}
// add UDFs
$result = $this->udflib->getDefinitionForModel($this->PrestudentModel);
+2 -13
View File
@@ -565,7 +565,7 @@ function openDialog(lehrveranstaltung_id, termin_id, lvBezeichnung, terminVon, t
* @param {type} termin_id ID des Prüfungstermines
* @returns {undefined}
*/
function saveAnmeldung(lehrveranstaltung_id, termin_id, force = false)
function saveAnmeldung(lehrveranstaltung_id, termin_id)
{
var uid = $("#anmeldung_hinzufuegen_uid").val();
if(lehrveranstaltung_id === undefined)
@@ -602,21 +602,10 @@ function saveAnmeldung(lehrveranstaltung_id, termin_id, force = false)
uid: uid,
studienverpflichtung_id: studienverpflichtung_id,
studiengang_kz: studiengang_kz,
ects: ects,
force: force
ects: ects
},
error: loadError,
success: function(data){
if (data?.confirm === 'true')
{
if (confirm(data.errormsg))
{
saveAnmeldung(lehrveranstaltung_id, termin_id, true);
}
return;
}
if(data.error === 'false')
{
messageBox("message", data.result, "green", "highlight", 10000);
@@ -394,7 +394,7 @@ function loadTermine()
*/
function saveAnmeldung($aktStudiensemester = null, $uid = null)
{
global $p, $rechte;
global $p;
$termin = new pruefungstermin($_REQUEST["termin_id"]);
$pruefung = new pruefung();
$lehrveranstaltung = new lehrveranstaltung($_REQUEST["lehrveranstaltung_id"]);
@@ -404,7 +404,6 @@ function saveAnmeldung($aktStudiensemester = null, $uid = null)
$studienverpflichtung_id = filter_input(INPUT_POST, "studienverpflichtung_id");
$studiengang_kz = filter_input(INPUT_POST, "studiengang_kz");
$ects = filter_input(INPUT_POST, "ects");
$force = filter_input(INPUT_POST, "force", FILTER_VALIDATE_BOOLEAN);
//Defaulteinstellung für Anzahlprüfungsversuche (wird durch Addon "ktu" überschrieben)
$maxAnzahlVersuche = 0;
@@ -558,28 +557,11 @@ function saveAnmeldung($aktStudiensemester = null, $uid = null)
$ects_verwendet += $lehrveranstaltung->ects;
$datum = new datum();
$kollision = ($datum->between($termin->von, $termin->bis, $temp->von)) || ($datum->between($termin->von, $termin->bis, $temp->bis));
if ($kollision)
if(($datum->between($termin->von, $termin->bis, $temp->von)) || ($datum->between($termin->von, $termin->bis, $temp->bis)))
{
$is_berechtigt = $rechte->isBerechtigt('lehre/pruefungsanmeldungAdmin');
if ($is_berechtigt)
{
if (!$force)
{
$data['result'][$temp->pruefungstermin_id] = "true";
$data['error'] = 'true';
$data['confirm'] = 'true';
$data['errormsg'] = $p->t('pruefung/kollisionMitAndererAnmeldungForce');
}
}
else
{
$data['result'][$temp->pruefungstermin_id] = "true";
$data['error'] = 'true';
$data['errormsg'] = $p->t('pruefung/kollisionMitAndererAnmeldung');
}
$data['result'][$temp->pruefungstermin_id] = "true";
$data['error'] = 'true';
$data['errormsg'] = $p->t('pruefung/kollisionMitAndererAnmeldung');
}
}
@@ -202,9 +202,9 @@ else if(isset($_GET["id"]) && $_GET["id"]!= null && isset($_GET["method"]) && $_
}
$method = $_GET["method"];
}
else if(isset($_POST["delete"]))
else if(isset($_GET["id"]) && $_GET["id"]!= null && isset($_GET["method"]) && $_GET["method"]=="delete")
{
$pruefungsfenster_id = $_POST["delete"];
$pruefungsfenster_id = $_GET["id"];
$pruefungsfenster = new pruefungsfenster();
$pruefungsfenster->load($pruefungsfenster_id);
@@ -226,7 +226,7 @@ else if(isset($_POST["delete"]))
{
echo $p->t('pruefung/pruefungsfensterKonnteNichtGeloeschtWerdenDaPruefungen');
}
$method = 'delete';
$method = $_GET["method"];
}
else
{
@@ -394,41 +394,39 @@ else
if(!empty($prfFenster->result)){
?>
<form method="POST" action="pruefungsfenster_anlegen.php">
<table class="tablesorter" id="prfTable">
<thead>
<tr>
<th><?php echo $p->t('global/studiensemester'); ?></th>
<th><?php echo $p->t('global/organisationseinheit'); ?></th>
<th><?php echo $p->t('pruefung/start'); ?></th>
<th><?php echo $p->t('pruefung/ende'); ?></th>
<th><?php echo $p->t('global/bearbeiten'); ?></th>
<th><?php echo $p->t('global/loeschen'); ?></th>
</tr>
</thead>
<tbody>
<?php
$organisationseinheit = new organisationseinheit();
foreach ($prfFenster->result as $result)
<table class="tablesorter" id="prfTable">
<thead>
<tr>
<th><?php echo $p->t('global/studiensemester'); ?></th>
<th><?php echo $p->t('global/organisationseinheit'); ?></th>
<th><?php echo $p->t('pruefung/start'); ?></th>
<th><?php echo $p->t('pruefung/ende'); ?></th>
<th><?php echo $p->t('global/bearbeiten'); ?></th>
<th><?php echo $p->t('global/loeschen'); ?></th>
</tr>
</thead>
<tbody>
<?php
$organisationseinheit = new organisationseinheit();
foreach ($prfFenster->result as $result)
{
if(in_array($result->oe_kurzbz, $oe))
{
if(in_array($result->oe_kurzbz, $oe))
{
$organisationseinheit->load($result->oe_kurzbz);
echo
'<tr>
<td>'.$result->studiensemester_kurzbz.'</td>
<td>'.$organisationseinheit->organisationseinheittyp_kurzbz." ".$organisationseinheit->bezeichnung.'</td>
<td>'.$result->start.'</td>
<td>'.$result->ende.'</td>
<td><a href="pruefungsfenster_anlegen.php?method=update&id='.$result->pruefungsfenster_id.'">'.$p->t('global/bearbeiten').'</a></td>
<td><button type="submit" name="delete" value="'. $result->pruefungsfenster_id .'">'. $p->t('global/loeschen') .'</button></td>
</tr>';
}
$organisationseinheit->load($result->oe_kurzbz);
echo
'<tr>
<td>'.$result->studiensemester_kurzbz.'</td>
<td>'.$organisationseinheit->organisationseinheittyp_kurzbz." ".$organisationseinheit->bezeichnung.'</td>
<td>'.$result->start.'</td>
<td>'.$result->ende.'</td>
<td><a href="pruefungsfenster_anlegen.php?method=update&id='.$result->pruefungsfenster_id.'">'.$p->t('global/bearbeiten').'</a></td>
<td><a href="pruefungsfenster_anlegen.php?method=delete&id='.$result->pruefungsfenster_id.'">'. $p->t('global/loeschen').'</a></td>
</tr>';
}
?>
</tbody>
</table>
</form>
}
?>
</tbody>
</table>
<?php
}
else
@@ -654,7 +654,7 @@ function checkCollision($uid, $beginn, $ende)
{
$collision = false;
$pruefung = new pruefungCis();
$pruefung->getAllPruefungen($uid, false);
$pruefung->getAllPruefungen($uid);
$datum = new datum();
foreach($pruefung->result as $prf)
{
@@ -56,24 +56,18 @@ $lehrveranstaltung->loadLVAfromMitarbeiter(0, $uid, $studiensemester->getaktorNe
if(empty($lehrveranstaltung->lehrveranstaltungen) && !$rechte->isBerechtigt('lehre/pruefungsterminAdmin'))
die('Sie haben keine Berechtigung für diese Seite');
if (isset($_GET['termin_id']))
{
if (!ctype_digit($_GET['termin_id']))
die('Wrong Parameter');
if (!ctype_digit($_GET['termin_id']))
die('Wrong Parameter');
else
$termin_id = $_GET['termin_id'];
}
else if (isset($_POST["speichern"]))
{
$termin_id = $_POST['termin_id'];
if (isset($_GET["speichern"]))
{
$prfgtermin = new pruefungstermin($termin_id);
$von = $_POST["datum"]." ".$_POST["vonzeit"];
$bis = $_POST["datum"]." ".$_POST["biszeit"];
$von = $_GET["datum"]." ".$_GET["vonzeit"];
$bis = $_GET["datum"]." ".$_GET["biszeit"];
$prfgtermin->von = $von;
$prfgtermin->bis = $bis;
$prfgtermin->teilnehmer_min = $_POST["terminmin"];
$prfgtermin->teilnehmer_max = $_POST["terminmax"];
$prfgtermin->save();
}
@@ -116,8 +110,6 @@ $datum = explode(" ", $prfgtermin->von)[0];
$vonzeit = substr(explode(" ", $prfgtermin->von)[1],0,5);
$biszeit = substr(explode(" ", $prfgtermin->bis)[1],0,5);
$pruefung_id = $prfgtermin->pruefung_id;
$teilnehmer_min = $prfgtermin->teilnehmer_min;
$teilnehmer_max = $prfgtermin->teilnehmer_max;
$pruefung = new pruefungCis($pruefung_id);
$pruefung->getLehrveranstaltungenByPruefung();
@@ -186,7 +178,7 @@ foreach ($anmeldungen as $row)
</head>
<body style="padding-top:20px">
<center>
<form name="change_termin" action="./pruefungstermin_aendern.php" method="POST">
<form name="change_termin" action="./pruefungstermin_aendern.php" method="GET">
<table>
<tr>
<td><?php echo $p->t('global/lehrveranstaltung'); ?></td>
@@ -204,15 +196,6 @@ foreach ($anmeldungen as $row)
<td><?php echo $p->t('global/bis'); ?></td>
<td><input type="text" name="biszeit" value="<?php echo $biszeit ?>" maxlength="5"></td>
</tr>
<tr>
<td><?php echo $p->t('pruefung/pruefungMinTeilnehmer'); ?></td>
<td>
<input type="number" name="terminmin" placeholder="0" min="0" value="<?php echo $teilnehmer_min; ?>"></td>
</tr>
<tr>
<td><?php echo $p->t('pruefung/pruefungMaxTeilnehmer'); ?></td>
<td><input type="number" name="terminmax" placeholder="0" min="0" value="<?php echo $teilnehmer_max; ?>"></td>
</tr>
<tr>
<td></td>
<td>
+4 -13
View File
@@ -525,11 +525,7 @@ class pruefungCis extends basis_db
return false;
}
$qry = 'UPDATE campus.tbl_pruefung
SET storniert=true,
updatevon='.$this->db_add_param($this->updatevon).',
updateamum= now()
WHERE pruefung_id='.$this->db_add_param($pruefung_id).';';
$qry = 'UPDATE campus.tbl_pruefung SET storniert=true WHERE pruefung_id='.$this->db_add_param($pruefung_id).';';
if(!$this->db_query($qry))
{
@@ -710,18 +706,13 @@ class pruefungCis extends basis_db
* @param String $mitarbeiter_uid UID des Mitarbeiters (optional)
* @return boolean
*/
public function getAllPruefungen($mitarbeiter_uid = NULL, $storniert = null)
public function getAllPruefungen($mitarbeiter_uid = NULL)
{
$qry = 'SELECT * FROM campus.tbl_pruefung WHERE 1 = 1';
$qry = 'SELECT * FROM campus.tbl_pruefung';
if(!is_null($mitarbeiter_uid))
{
$qry .= ' AND mitarbeiter_uid='.$this->db_add_param($mitarbeiter_uid);
}
if(!is_null($storniert))
{
$qry .= ' AND storniert = '.$this->db_add_param($storniert, FHC_BOOLEAN);
$qry .= ' WHERE mitarbeiter_uid='.$this->db_add_param($mitarbeiter_uid);
}
$qry .= ';';
-1
View File
@@ -124,7 +124,6 @@ $this->phrasen['pruefung/anmeldungenVerwalten'] = 'Anmeldungen Verwalten';
$this->phrasen['pruefung/anmeldungErfolgreich'] = 'Anmeldung erfolgreich!';
$this->phrasen['pruefung/zuWenigeCreditPoints'] = 'Credit-Points-Guthaben ist zu gering.';
$this->phrasen['pruefung/kollisionMitAndererAnmeldung'] = 'Kollision mit anderer Anmeldung.';
$this->phrasen['pruefung/kollisionMitAndererAnmeldungForce'] = 'Kollision mit anderer Anmeldung. Trotzdem fortfahren?';
$this->phrasen['pruefung/keineFreienPlaetzeVorhanden'] = 'Keine freien Plätze vorhanden.';
$this->phrasen['pruefung/anmeldungAufgrundVonSperreNichtMoeglich'] = 'Anmeldung auf Grund von Sperre nicht möglich.';
$this->phrasen['pruefung/prestudentNichtGefunden'] = 'Prestudent nicht gefunden.';
-1
View File
@@ -123,7 +123,6 @@ $this->phrasen['pruefung/anmeldungenVerwalten'] = 'Anmeldungen Verwalten';
$this->phrasen['pruefung/anmeldungErfolgreich'] = 'Registration successful.';
$this->phrasen['pruefung/zuWenigeCreditPoints'] = 'Credit-Points-Guthaben ist zu gering.';
$this->phrasen['pruefung/kollisionMitAndererAnmeldung'] = 'Kollision mit anderer Anmeldung.';
$this->phrasen['pruefung/kollisionMitAndererAnmeldungForce'] = 'Kollision mit anderer Anmeldung. Trotzdem fortfahren?';
$this->phrasen['pruefung/keineFreienPlaetzeVorhanden'] = 'Keine freien Plätze vorhanden.';
$this->phrasen['pruefung/anmeldungAufgrundVonSperreNichtMoeglich'] = 'Anmeldung auf Grund von Sperre nicht möglich.';
$this->phrasen['pruefung/prestudentNichtGefunden'] = 'Prestudent nicht gefunden.';
+37
View File
@@ -0,0 +1,37 @@
export default {
getAllStudienSemester(studiensemester=undefined, studiengang=undefined, semester=undefined, studienplan=undefined) {
return {
method: 'get',
url: '/api/frontend/v1/Studium/getAllStudienSemester',
params: {studiensemester, studiengang, semester, studienplan}
};
},
getStudiengaengeForStudienSemester(studiensemester) {
return {
method: 'get',
url: `/api/frontend/v1/Studium/getStudiengaengeForStudienSemester/${studiensemester}`,
};
},
getStudienplaeneBySemester(studiengang, studiensemester) {
return {
method: 'get',
url: `/api/frontend/v1/Studium/getStudienplaeneBySemester`,
params: {
studiengang,
studiensemester,
}
};
},
getLvPlanForStudiensemester(studiensemester_kurzbz, lvid) {
return {
method: 'get',
url: `/api/frontend/v1/LvPlan/getLvPlanForStudiensemester/${studiensemester}/${lvid}`,
};
},
getLvEvaluierungInfo(studiensemester_kurzbz, lvid) {
return {
method: 'get',
url: `/api/frontend/v1/Studium/getLvEvaluierungInfo/${studiensemester_kurzbz}/${lvid}`,
};
}
};
+4 -3
View File
@@ -1,9 +1,9 @@
import FhcSearchbar from "../components/searchbar/searchbar.js";
import CisMenu from "../components/Cis/Menu.js";
import PluginsPhrasen from '../plugins/Phrasen.js';
import ApiSearchbar from '../api/factory/searchbar.js';
import Theme from "../plugins/Theme.js";
import FhcBase from '../plugins/FhcBase/FhcBase.js';
// import PluginsPhrasen from '../plugins/Phrasen.js';
const app = Vue.createApp({
name: 'CisApp',
components: {
@@ -148,6 +148,7 @@ app.use(primevue.config.default, {
tooltip: 8000
}
})
app.use(PluginsPhrasen);
// app.use(PluginsPhrasen);
app.use(FhcBase);
app.use(Theme);
app.mount('#cis-header');
+2 -1
View File
@@ -1,5 +1,6 @@
import PluginsPhrasen from '../../plugins/Phrasen.js';
import {setScrollbarWidth} from "../../helpers/CssVarCalcHelpers.js";
import FhcBase from "../../plugins/FhcBase/FhcBase.js";
const app = Vue.createApp({
name: 'DocumentsApp',
@@ -89,5 +90,5 @@ const app = Vue.createApp({
setScrollbarWidth();
app.use(PluginsPhrasen);
app.use(FhcBase);
app.mount('#content');
+2 -3
View File
@@ -1,7 +1,6 @@
import ProfilUpdateView from "../../components/Cis/ProfilUpdate/ProfilUpdateView.js";
import PluginsPhrasen from "../../plugins/Phrasen.js";
import ApiProfilUpdate from '../../api/factory/profilUpdate.js';
import FhcBase from "../../plugins/FhcBase/FhcBase.js";
// TODO: sobald in verwendung den vue router pfad zu ProfilUpdateView definieren und diese app in component auslagern
const app = Vue.createApp({
@@ -34,4 +33,4 @@ const app = Vue.createApp({
});
},
});
app.use(PluginsPhrasen).mount("#content");
app.use(FhcBase).mount("#content");
+2 -2
View File
@@ -1,6 +1,6 @@
import {CoreNavigationCmpt} from '../../components/navigation/Navigation.js';
import DashboardAdmin from '../../components/Dashboard/Admin.js';
import PluginsPhrasen from '../../plugins/Phrasen.js';
import FhcBase from "../../plugins/FhcBase/FhcBase.js";
const app = Vue.createApp({
name: 'AdminApp',
@@ -12,5 +12,5 @@ const app = Vue.createApp({
DashboardAdmin
}
});
app.use(PluginsPhrasen);
app.use(FhcBase);
app.mount('#main');
+3 -2
View File
@@ -1,5 +1,6 @@
import FhcDashboard from '../../components/Dashboard/Dashboard.js';
import PluginsPhrasen from '../../plugins/Phrasen.js';
import FhcBase from '../../plugins/FhcBase/FhcBase.js';
import Theme from '../../plugins/Theme.js';
import contrast from '../../directives/contrast.js';
import {setScrollbarWidth} from "../../helpers/CssVarCalcHelpers.js";
@@ -330,7 +331,7 @@ app.use(primevue.config.default, {
}
})
app.directive('tooltip', primevue.tooltip);
app.use(PluginsPhrasen);
app.use(FhcBase);
app.use(Theme);
app.directive('contrast', contrast);
app.mount('#fhccontent');
+2 -2
View File
@@ -1,6 +1,6 @@
import {CoreNavigationCmpt} from '../components/navigation/Navigation.js';
import DashboardAdmin from '../components/Dashboard/Admin.js';
import Phrases from "../plugin/Phrasen.js"
import FhcBase from "../plugins/FhcBase/FhcBase.js";
Vue.createApp({
name: 'DashboardAdminApp',
@@ -13,4 +13,4 @@ Vue.createApp({
},
mounted() {
}
}).use(Phrases).mount('#main');
}).use(FhcBase).mount('#main');
+2 -2
View File
@@ -1,5 +1,5 @@
import LVVerwaltung from "../components/LVVerwaltung/LVVerwaltung.js";
import Phrasen from "../plugins/Phrasen.js";
import FhcBase from "../plugins/FhcBase/FhcBase.js";
const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
@@ -98,5 +98,5 @@ app
overlay: 1100
}
})
.use(Phrasen)
.use(FhcBase)
.mount('#main');
+2 -2
View File
@@ -21,7 +21,7 @@ import {LogsViewerTabulatorEventHandlers} from './TabulatorSetup.js';
import {CoreFilterCmpt} from '../../components/filter/Filter.js';
import {CoreNavigationCmpt} from '../../components/navigation/Navigation.js';
import PluginsPhrasen from '../../plugins/Phrasen.js';
import FhcBase from "../../plugins/FhcBase/FhcBase.js";
const logsViewerApp = Vue.createApp({
data: function() {
@@ -42,5 +42,5 @@ const logsViewerApp = Vue.createApp({
}
});
logsViewerApp.use(PluginsPhrasen).mount('#main');
logsViewerApp.use(FhcBase).mount('#main');
+2 -2
View File
@@ -1,7 +1,7 @@
//TODO Manu
//use this instead of Nachrichten.js
import NewMessage from "../../components/Messages/Details/NewMessage/NewDiv.js";
import Phrasen from "../../plugin/Phrasen.js";
import FhcBase from "../../plugins/FhcBase/FhcBase.js";
const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
@@ -23,5 +23,5 @@ app
overlay: 1100
}
})
.use(Phrasen)
.use(FhcBase)
.mount('#main');
+2 -3
View File
@@ -1,6 +1,5 @@
import NewMessage from "../components/Messages/Details/NewMessage/NewDiv.js";
import Phrasen from "../plugins/Phrasen.js";
import FhcBase from "../plugins/FhcBase/FhcBase.js";
const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
@@ -23,5 +22,5 @@ app
overlay: 1100
}
})
.use(Phrasen)
.use(FhcBase)
.mount('#main');
+2 -5
View File
@@ -16,10 +16,7 @@
*/
import FhcStudentenverwaltung from "../components/Stv/Studentenverwaltung.js";
import fhcapifactory from "./api/fhcapifactory.js";
import PluginsPhrasen from "../plugins/Phrasen.js";
import FhcBase from "../plugins/FhcBase/FhcBase.js";
const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
@@ -217,5 +214,5 @@ app
overlay: 1100
}
})
.use(PluginsPhrasen)
.use(FhcBase)
.mount('#main');
+2 -2
View File
@@ -1,7 +1,7 @@
import StudierendenantragAntrag from "../../components/Studierendenantrag/Antrag.js";
import StudierendenantragStatus from "../../components/Studierendenantrag/Status.js";
import StudierendenantragInfoblock from "../../components/Studierendenantrag/Infoblock.js";
import PluginsPhrasen from '../../plugins/Phrasen.js';
import FhcBase from "../../plugins/FhcBase/FhcBase.js";
const app = Vue.createApp({
name: 'AntragApp',
@@ -21,5 +21,5 @@ const app = Vue.createApp({
}
});
app
.use(PluginsPhrasen)
.use(FhcBase)
.mount('#wrapper');
+2 -2
View File
@@ -1,5 +1,5 @@
import StudierendenantragLeitung from '../../../components/Studierendenantrag/Leitung.js';
import PluginsPhrasen from '../../../plugins/Phrasen.js';
import FhcBase from "../../../plugins/FhcBase/FhcBase.js";
const app = Vue.createApp({
name: 'LeitungApp',
@@ -8,6 +8,6 @@ const app = Vue.createApp({
}
});
app
.use(PluginsPhrasen)
.use(FhcBase)
.use(primevue.config.default,{zIndex: {overlay: 9999}})
.mount('#wrapper');
+2 -2
View File
@@ -1,5 +1,5 @@
import LvZuweisung from '../../../components/Studierendenantrag/Lvzuweisung.js';
import PluginsPhrasen from '../../../plugins/Phrasen.js';
import FhcBase from "../../../plugins/FhcBase/FhcBase.js";
const app = Vue.createApp({
name: 'LvzuweisungApp',
@@ -13,5 +13,5 @@ const app = Vue.createApp({
}
});
app
.use(PluginsPhrasen)
.use(FhcBase)
.mount('#wrapper');
+2 -2
View File
@@ -1,5 +1,5 @@
import LvPopup from '../../../components/Studierendenantrag/Leitung/LvPopup.js';
import PluginsPhrasen from '../../../plugins/Phrasen.js';
import FhcBase from "../../../plugins/FhcBase/FhcBase.js";
const app = Vue.createApp({
name: 'StudentApp',
@@ -8,5 +8,5 @@ const app = Vue.createApp({
}
});
app
.use(PluginsPhrasen)
.use(FhcBase)
.mount('#wrapper');
@@ -17,7 +17,7 @@
import LvTemplateUebersicht from '../../../lehre/lvplanung/LvTemplateUebersicht.js';
import {CoreNavigationCmpt} from '../../../components/navigation/Navigation.js';
import PluginsPhrasen from "../../../plugins/Phrasen.js";
import FhcBase from "../../../plugins/FhcBase/FhcBase.js";
const lvTemplatesApp = Vue.createApp({
@@ -30,5 +30,5 @@ const lvTemplatesApp = Vue.createApp({
lvTemplatesApp
.use(primevue.config.default,{zIndex: {overlay: 9999}})
.use(PluginsPhrasen)
.use(FhcBase)
.mount('#main')
-1
View File
@@ -3,7 +3,6 @@ import FhcSearchbar from "../searchbar/searchbar.js";
import CisSprachen from "./Sprachen.js"
import ThemeSwitch from "./ThemeSwitch.js";
import ApiCisMenu from '../../api/factory/cis/menu.js';
export default {
components: {
CisMenuEntry,
@@ -2,9 +2,10 @@ import BsModal from "../../Bootstrap/Modal.js";
import LvMenu from "./LvMenu.js";
import ApiAddons from '../../../api/factory/addons.js';
import ApiStudium from '../../../api/factory/studium.js';
export default {
name: "LvUebersicht",
props:{
event:{
type:Object,
@@ -63,7 +64,7 @@ export default {
// check lv evaluierung info
if (this.studium_studiensemester) {
this.$fhcApi.factory.studium.getLvEvaluierungInfo(this.studium_studiensemester, this.event.lehreinheit_id ?? this.event.lehrveranstaltung_id)
this.$api.call(ApiStudium.getLvEvaluierungInfo(this.studium_studiensemester, this.event.lehreinheit_id ?? this.event.lehrveranstaltung_id))
.then(data => data.data)
.then(res => {
this.lvEvaluierungMessage = res.message;
@@ -72,7 +73,7 @@ export default {
// check if the lv has lvplan entries for this studiensemester
if (this.studiensemester && this.event) {
return this.$fhcApi.factory.studium.getLvPlanForStudiensemester(this.studiensemester, this.event.lehreinheit_id ?? this.event.lehrveranstaltung_id)
this.$api.call(ApiStudium.getLvPlanForStudiensemester(this.studiensemester, this.event.lehreinheit_id ?? this.event.lehrveranstaltung_id))
.then(data => data.data)
.then(res => {
if (Array.isArray(res) && res.length > 0) {
@@ -31,7 +31,7 @@ export default {
this.event.lektor.slice(0, 3).map(lektor => lektor.kurzbz).join("\n")
+ "\n" + this.$p.t('lehre/weitereLektoren', [this.event.lektor.length - 3])
].join(": "));
} else {console.log(this.event.lektor);
} else {
tooltipArray.push([
this.$p.t('lehre/lektor'),
this.event.lektor.map(lektor => lektor.kurzbz).join("\n")
+8 -7
View File
@@ -1,7 +1,8 @@
import LvUebersicht from "../Mylv/LvUebersicht.js";
import ApiStudium from '../../../api/factory/studium.js';
export default {
name: "Studium",
data(){
return {
studienSemester :[],
@@ -97,28 +98,28 @@ export default {
return value;
},
changeSelectedStudienSemester(studiensemester_kurzbz) {
this.$fhcApi.factory.studium.getAllStudienSemester(studiensemester_kurzbz, this.selectedStudiengang, this.selectedSemester, this.selectedStudienordnung)
this.$api.call(ApiStudium.getAllStudienSemester(studiensemester_kurzbz, this.selectedStudiengang, this.selectedSemester, this.selectedStudienordnung))
.then(data => data.data)
.then(res => {
this.extractPropertyValues(res);
})
},
changeSelectedStudienGang(studiengang_kz) {
this.$fhcApi.factory.studium.getAllStudienSemester(this.selectedStudiensemester, studiengang_kz, this.selectedSemester, this.selectedStudienordnung)
this.$api.call(ApiStudium.getAllStudienSemester(this.selectedStudiensemester, studiengang_kz, this.selectedSemester, this.selectedStudienordnung))
.then(data => data.data)
.then(res => {
this.extractPropertyValues(res);
})
},
changeSelectedSemester(semester) {
this.$fhcApi.factory.studium.getAllStudienSemester(this.selectedStudiensemester, this.selectedStudiengang, semester, this.selectedStudienordnung)
this.$api.call(ApiStudium.getAllStudienSemester(this.selectedStudiensemester, this.selectedStudiengang, semester, this.selectedStudienordnung))
.then(data => data.data)
.then(res => {
this.extractPropertyValues(res);
})
},
changeSelectedStudienPlan(studienplan_id) {
this.$fhcApi.factory.studium.getAllStudienSemester(this.selectedStudiensemester, this.selectedStudiengang, this.selectedSemester, studienplan_id)
this.$api.call(ApiStudium.getAllStudienSemester(this.selectedStudiensemester, this.selectedStudiengang, this.selectedSemester, studienplan_id))
.then(data => data.data)
.then(res => {
this.extractPropertyValues(res);
@@ -256,8 +257,8 @@ export default {
const studienordnung = JSON.parse(this.getDataFromLocalStorage("studienordnung")) ?? undefined;
// only fetch default data if no data is stored in the local storage
this.$fhcApi.factory.studium.getAllStudienSemester(studiensemester, studiengang, semester, studienordnung)
this.$api.call(ApiStudium.getAllStudienSemester(studiensemester, studiengang, semester, studienordnung))
.then(data => data.data)
.then(res => {
this.extractPropertyValues(res);
@@ -32,15 +32,15 @@ export default {
},
hasZGVBakkPermission: {
from: 'hasZGVBakkPermission',
default: []
default: false
},
hasZGVMasterPermission: {
from: 'hasZGVMasterPermission',
default: []
default: false
},
hasZGVDoctorPermission: {
from: 'hasZGVDoctorPermission',
default: []
default: false
},
hasBismeldenPermission: {
from: 'hasBismeldenPermission',
@@ -89,15 +89,6 @@ export default {
computed: {
deltaLength() {
return Object.keys(this.deltaArray).length;
},
disableZgvBakk: function() {
return !this.hasZGVBakkPermission.includes(this.modelValue.studiengang_kz.toString());
},
disableZgvMaster: function() {
return !this.hasZGVMasterPermission.includes(this.modelValue.studiengang_kz.toString());
},
disableZgvDoctor: function() {
return !this.hasZGVDoctorPermission.includes(this.modelValue.studiengang_kz.toString());
}
},
watch: {
@@ -298,7 +289,7 @@ export default {
dropdown
name="zgv_code"
@complete="filterZgvs"
:disabled="disableZgvBakk"
:disabled="!hasZGVBakkPermission"
>
<template #option="slotProps">
<div
@@ -317,6 +308,7 @@ export default {
type="text"
v-model="data.zgvort"
name="zgvort"
:disabled="!hasZGVBakkPermission"
>
</form-input>
<form-input
@@ -333,6 +325,7 @@ export default {
format="dd.MM.yyyy"
preview-format="dd.MM.yyyy"
:teleport="true"
:disabled="!hasZGVBakkPermission"
>
</form-input>
<form-input
@@ -342,6 +335,7 @@ export default {
type="select"
v-model="data.zgvnation"
name="zgvnation"
:disabled="!hasZGVBakkPermission"
>
<!-- TODO(chris): gesperrte nationen können nicht ausgewählt werden! Um das zu realisieren müsste man ein pseudo select machen -->
<option value="">&nbsp;</option>
@@ -362,7 +356,7 @@ export default {
dropdown
name="zgvmas_code"
@complete="filterMasterZgvs"
:disabled="disableZgvMaster"
:disabled="!hasZGVMasterPermission"
>
<template #option="slotProps">
<div
@@ -381,6 +375,7 @@ export default {
type="text"
v-model="data.zgvmaort"
name="zgvmaort"
:disabled="!hasZGVMasterPermission"
>
</form-input>
<form-input
@@ -397,6 +392,7 @@ export default {
format="dd.MM.yyyy"
preview-format="dd.MM.yyyy"
:teleport="true"
:disabled="!hasZGVMasterPermission"
>
</form-input>
<form-input
@@ -406,6 +402,7 @@ export default {
type="select"
v-model="data.zgvmanation"
name="zgvmanation"
:disabled="!hasZGVMasterPermission"
>
<!-- TODO(chris): gesperrte nationen können nicht ausgewählt werden! Um das zu realisieren müsste man ein pseudo select machen -->
<option value="">&nbsp;</option>
@@ -427,7 +424,7 @@ export default {
dropdown
name="zgvdoktor_code"
@complete="filterDoktorZgvs"
:disabled="disableZgvDoctor"
:disabled="!hasZGVDoctorPermission"
>
<template #option="slotProps">
<div
@@ -446,6 +443,7 @@ export default {
type="text"
v-model="data.zgvdoktorort"
name="zgvdoktorort"
:disabled="!hasZGVDoctorPermission"
>
</form-input>
<form-input
@@ -462,6 +460,7 @@ export default {
format="dd.MM.yyyy"
preview-format="dd.MM.yyyy"
:teleport="true"
:disabled="!hasZGVDoctorPermission"
>
</form-input>
<form-input
@@ -471,6 +470,7 @@ export default {
type="select"
v-model="data.zgvdoktornation"
name="zgvdoktornation"
:disabled="!hasZGVDoctorPermission"
>
<!-- TODO(chris): gesperrte nationen können nicht ausgewählt werden! Um das zu realisieren müsste man ein pseudo select machen -->
<option value="">&nbsp;</option>
@@ -487,6 +487,7 @@ export default {
type="checkbox"
v-model="data.zgv_erfuellt"
name="zgv_erfuellt"
:disabled="!hasZGVBakkPermission"
>
</form-input>
</div>
@@ -498,6 +499,7 @@ export default {
type="checkbox"
v-model="data.zgvmas_erfuellt"
name="zgvmas_erfuellt"
:disabled="!hasZGVMasterPermission"
>
</form-input>
</div>
@@ -509,6 +511,7 @@ export default {
type="checkbox"
v-model="data.zgvdoktor_erfuellt"
name="zgvdoktor_erfuellt"
:disabled="!hasZGVDoctorPermission"
>
</form-input>
</div>
@@ -91,15 +91,7 @@ export default{
layoutColumnsOnNewData: false,
height: 'auto',
index: 'pruefung_id',
persistenceID: 'stv-details-pruefung-list-2026012701',
persistence: {
sort: false,
columns: ["width", "visible", "frozen"],
filter: false,
headerFilter: false,
group: false,
page: false
}
persistenceID: 'stv-details-pruefung-list-2025112402'
},
tabulatorEvents: [
{
+359
View File
@@ -0,0 +1,359 @@
import PvConfig from "../../../../index.ci.php/public/js/components/primevue/config/config.esm.min.js";
import PvToast from "../../../../index.ci.php/public/js/components/primevue/toast/toast.esm.min.js";
import PvConfirm from "../../../../index.ci.php/public/js/components/primevue/confirmdialog/confirmdialog.esm.min.js";
import PvConfirmationService from "../../../../index.ci.php/public/js/components/primevue/confirmationservice/confirmationservice.esm.min.js";
import {CoreRESTClient} from "../../RESTClient.js";
export default {
init(app, $p) {
let resolveReady;
const readyPromise = new Promise(resolve => { resolveReady = resolve; });
const helperAppContainer = document.createElement('div');
document.body.appendChild(helperAppContainer);
const helperApp = Vue.createApp({
name: "FhcAlertApp",
components: { PvToast, PvConfirm },
methods: {
mailToUrl(slotProps) {
let mailTo = FHC_JS_DATA_STORAGE_OBJECT.systemerror_mailto;
let subject = 'Meldung%20Systemfehler';
let body = `
Danke, dass Sie uns den Fehler melden. %0D%0A %0D%0A
Bitte beschreiben Sie uns ausführlich das Problem.%0D%0A
Bsp: Ich habe X ausgewählt und Y angelegt. Beim Speichern erhielt ich die Fehlermeldung. [Optional: Ich habe den Browser Z verwendet.]%0D%0A
-----------------------------------------------------------------------------------------------------------------------------------%0D%0A
PROBLEM: ... %0D%0A %0D%0A %0D%0A
-----------------------------------------------------------------------------------------------------------------------------------%0D%0A
Fehler URL: ` + FHC_JS_DATA_STORAGE_OBJECT.called_path + '/' + FHC_JS_DATA_STORAGE_OBJECT.called_method + `%0D%0A
Fehler Meldung: ` + slotProps.message.detail + `%0D%0A
-----------------------------------------------------------------------------------------------------------------------------------%0D%0A %0D%0A
Wir kümmern uns um eine rasche Behebung des Problems!`
return "mailto:" + mailTo + "?subject=" + subject + "&body=" + body;
},
openMessagecard(e) {
bootstrap.Collapse.getOrCreateInstance(e.target.getAttribute('href')).toggle();
}
},
computed: {
showmaillink() { return FHC_JS_DATA_STORAGE_OBJECT.systemerror_mailto !== ''; }
},
template: /* html */`
<pv-toast ref="toast" class="fhc-alert" :base-z-index="99999">
<template #message="{ message }">
<!--span :class="slotProps.iconClass"></span-->
<div class="p-toast-message-text">
<span class="p-toast-summary">{{ message.summary }}</span>
<div v-if="message.detail && message.html" class="p-toast-detail" v-html="message.detail"></div>
<div v-else-if="message.detail" class="p-toast-detail">{{ message.detail }}</div>
</div>
</template>
</pv-toast>
<pv-toast ref="alert" class="fhc-alert" :base-z-index="99999" position="center">
<template #message="slotProps">
<i class="fa fa-circle-exclamation fa-2xl mt-3"></i>
<div class="p-toast-message-text">
<span class="p-toast-summary">{{slotProps.message.summary}}</span>
<div class="p-toast-detail my-3">Sorry! Ein interner technischer Fehler ist aufgetreten.</div>
<div class="d-flex justify-content-between align-items-center">
<a
class="align-bottom flex-fill me-2"
data-bs-toggle="collapse"
:href="'#fhcAlertCollapseMessageCard' + slotProps.message.id"
role="button"
aria-expanded="false"
:aria-controls="'fhcAlertCollapseMessageCard' + slotProps.message.id"
@click="openMessagecard"
>
Fehler anzeigen
</a>
<a
v-if="showmaillink"
class="btn btn-primary flex-fill"
target="_blank"
:href="mailToUrl(slotProps)"
>
Fehler melden
</a>
</div>
<div ref="messageCard" :id="'fhcAlertCollapseMessageCard' + slotProps.message.id" class="collapse mt-3">
<div class="card card-body text-body small alertCollapseText">
{{slotProps.message.detail}}
</div>
</div>
</div>
</template>
</pv-toast>
<pv-confirm group="fhcAlertConfirm"></pv-confirm>`
});
// Link the helper app to the main app's phrasen system
helperApp.config.globalProperties.$p = $p;
helperApp.use(PvConfig);
helperApp.use(PvConfirmationService);
const helperAppInstance = helperApp.mount(helperAppContainer);
const $fhcAlert = {
// Internal storage for cross-plugin dependencies
deps: { $p },
ready: readyPromise,
// Method used by FhcBase to inject the API later
setDeps(deps) {
Object.assign(this.deps, deps);
// Alert is ready when both Phrases and API are present
if (this.deps.$p && this.deps.$api) resolveReady();
},
alertSuccess(message) {
if (Array.isArray(message))
return message.forEach(this.alertSuccess);
helperAppInstance.$refs.toast.add({ severity: 'success', summary: 'Info', detail: message, life: 1000});
},
alertInfo(message) {
if (Array.isArray(message))
return message.forEach(this.alertInfo);
helperAppInstance.$refs.toast.add({ severity: 'info', summary: 'Info', detail: message, life: 3000 });
},
alertWarning(message) {
if (Array.isArray(message))
return message.forEach(this.alertWarning);
helperAppInstance.$refs.toast.add({ severity: 'warn', summary: 'Achtung', detail: message});
},
alertError(message) {
if (Array.isArray(message))
return message.forEach(this.alertError);
helperAppInstance.$refs.toast.add({ severity: 'error', summary: 'Achtung', detail: message });
},
async alertSystemError(message) {
//TODO(Manu) for translation of content of template: restructure in data
//and update definitions with translations
await this.ready;
if (Array.isArray(message))
return message.forEach(this.alertSystemError);
helperAppInstance.$refs.alert.add({
severity: 'error',
summary: Vue.computed(() => this.deps.$p.t('alert/systemerror')),
detail: message});
},
async confirmDelete() {
await this.ready;
return new Promise((resolve, reject) => {
helperAppInstance.$confirm.require({
group: 'fhcAlertConfirm',
header: Vue.computed(() => this.deps.$p.t('alert/attention')),
message: Vue.computed(() => this.deps.$p.t('alert/confirm_delete')),
acceptLabel: Vue.computed(() => this.deps.$p.t('ui/loeschen')),
acceptClass: 'p-button-danger',
rejectLabel: Vue.computed(() => this.deps.$p.t('ui/abbrechen')),
rejectClass: 'p-button-secondary',
accept() {
resolve(true);
},
reject() {
resolve(false);
},
});
});
},
confirm(options) {
return new Promise((resolve, reject) => {
helperAppInstance.$confirm.require({
group: options?.group ?? 'fhcAlertConfirm',
header: options?.header ?? Vue.computed(() => this.deps.$p.t('alert/attention')),
message: options?.message ?? '',
acceptLabel: options?.acceptLabel ?? 'Ok',
acceptClass: options?.acceptClass ?? 'btn btn-primary',
rejectLabel: options?.rejectLabel ?? Vue.computed(() => this.deps.$p.t('ui/abbrechen')),
rejectClass: options?.rejectClass ?? 'btn btn-outline-secondary',
accept() {
resolve(true);
},
reject() {
resolve(false);
},
});
});
},
alertDefault(severity, title, message, sticky = false, html = false) {
let options = { severity: severity, summary: title, detail: message, html };
if (!sticky)
options.life = 3000;
helperAppInstance.$refs.toast.add(options);
},
alertMultiple(messageArray, severity = 'info', title = 'Info', sticky = false, html = false){
// Check, if array has only string values
if (messageArray.every(message => typeof message === 'string')) {
messageArray.forEach(message => this.alertDefault(severity, title, message, sticky, html));
return true;
}
return false;
},
handleSystemError(error) {
// don't show an error message to the user if the error was an aborted request
if(error.hasOwnProperty('name') && error.name.toLowerCase() === "AbortError".toLowerCase())
return;
// Error is string
if (typeof error === 'string')
return this.alertSystemError(error);
// Error is array of strings
if (Array.isArray(error) && error.every(err => typeof err === 'string'))
return error.every(this.alertSystemError);
// Error has been handled already
if (error.hasOwnProperty('handled') && error.handled)
return;
// Error is object
if (typeof error === 'object' && error !== null) {
let errMsg = '';
if (error.hasOwnProperty('response') && error.response?.data?.retval)
errMsg += 'Error Message: ' + (error.response.data.retval.message || error.response.data.retval) + '\r\n';
else if (error.hasOwnProperty('message'))
errMsg += 'Error Message: ' + error.message.toUpperCase() + '\r\n';
if (error.hasOwnProperty('config') && error.config.hasOwnProperty('url'))
errMsg += 'Error ConfigURL: ' + error.config.url + '\r\n';
if (error.hasOwnProperty('stack'))
errMsg += 'Error Stack: ' + error.stack + '\r\n';
// Fallback object error message
if (errMsg == '')
errMsg = 'Error Message: ' + JSON.stringify(error) + '\r\n';
errMsg += 'Error Controller Path: ' + FHC_JS_DATA_STORAGE_OBJECT.called_path + '/' + FHC_JS_DATA_STORAGE_OBJECT.called_method;
return this.alertSystemError(errMsg);
}
// Fallback
this.alertSystemError('alertSystemError throws Generic Error\r\nError Controller Path: ' + FHC_JS_DATA_STORAGE_OBJECT.called_path + '/' + FHC_JS_DATA_STORAGE_OBJECT.called_method);
},
handleSystemMessage(message) {
// Message is string
if (typeof message === 'string')
return this.alertWarning(message);
// Message is array of strings
if (Array.isArray(message)) {
// If Array has only Strings
if (message.every(msg => typeof msg === 'string'))
return message.every(this.alertWarning);
// If Array has only Objects
if (message.every(msg => typeof msg === 'object') && msg !== null) {
return message.every(msg => {
if (msg.hasOwnProperty('data') && msg.data.hasOwnProperty('retval')) {
this.alertWarning(JSON.stringify(msg.data.retval));
} else {
this.alertSystemError(JSON.stringify(msg));
}
});
}
}
// Message is Object with data property
if (typeof message === 'object' && message !== null){
if (message.hasOwnProperty('data') && message.data.hasOwnProperty('retval')) {
// NOTE(chris): changed: alertSystemError => alertWarning
this.alertWarning(JSON.stringify(message.data.retval));
} else {
this.alertSystemError(JSON.stringify(message));
}
return;
}
// Fallback
this.alertSystemError('alertSystemError throws Generic Error\r\nError Controller Path: ' + FHC_JS_DATA_STORAGE_OBJECT.called_path + '/' + FHC_JS_DATA_STORAGE_OBJECT.called_method);
},
resetFormValidation(form) {
const event = new Event('fhc-form-reset');
form.querySelectorAll(['[data-fhc-form-validate],[data-fhc-form-error]']).forEach(el => el.dispatchEvent(event));
/*const alert = form.querySelector('div.alert.alert-danger[role="alert"]');
if (alert) {
alert.innerHTML = '';
alert.classList.add('d-none');
}
form.querySelectorAll('.invalid-feedback').forEach(n => n.remove());
form.querySelectorAll('.is-invalid').forEach(n => n.classList.remove('is-invalid'));
form.querySelectorAll('.is-valid').forEach(n => n.classList.remove('is-valid'));*/
},
handleFormValidation(error, form) {
if (form === undefined) {
if (error && error.nodeType === Node.ELEMENT_NODE)
return err => this.handleFormValidation(err, error);
} else {
if (error?.response?.status == 400) {
let errors = CoreRESTClient.getError(error.response.data);
if (typeof errors !== "object")
errors = error.response.data;
// NOTE(chris): reset form validation
this.resetFormValidation(form);
// NOTE(chris): set form input validation
const notFound = Object.entries(errors).filter(([key, detail]) => {
const input = form.querySelector('[data-fhc-form-validate="' + key + '"]');
if (!input)
return true;
input.dispatchEvent(new CustomEvent('fhc-form-invalidate', {detail}));
/*const input = form.querySelector('[name="' + key + '"]');
if (!input)
return true;
input.classList.add('is-invalid');
const feedback = document.createElement('div');
feedback.classList.add('invalid-feedback');
feedback.innerHTML = detail;
input.after(feedback);*/
return false;
}).map(arr => arr[1]);
//const alert = form.querySelector('div.alert.alert-danger[role="alert"]');
const alert = form.querySelector('[data-fhc-form-error]');
if (alert && notFound.length) {
alert.dispatchEvent(new CustomEvent('fhc-form-error', {detail: notFound}));
/*notFound.forEach(txt => {
const p = document.createElement('p');
p.innerHTML = txt;
alert.append(p);
});
if (notFound.length) {
alert.lastChild.classList.add('mb-0');
alert.classList.remove('d-none');
}*/
} else {
notFound.forEach(this.alertError);
}
return;
}
}
if (error?.response?.status == 400) {
let errors = CoreRESTClient.getError(error.response.data);
this.alertError((typeof errors === 'object') ? Object.values(errors) : errors);
} else {
this.handleSystemError(error);
}
}
};
return $fhcAlert;
}
};
+591
View File
@@ -0,0 +1,591 @@
/**
* BaseApi.js - Contains the original api plugin methods call, get, post, etc.
* Dependencies like phrasen($p) or alert($fhcAlert) are optional and can be injected at initialization
* or at a later stage.
*/
export class BaseApi {
constructor(deps = {}) {
// optional dependencies like { $fhcAlert: ..., $p: ... }
this.deps = deps;
this.resolveReady = null;
this.ready = new Promise(resolve => { this.resolveReady = resolve; });
// If deps were passed in constructor, resolve immediately
if (this.deps.$fhcAlert && this.deps.$p) this.resolveReady();
this.setupDefaultConfig()
this.axiosInstance = axios.create({
timeout: 500000,
baseURL: FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + "/"
});
this.setupInterceptors();
}
// === public functions START ===
// in case of api instantiation before vue app mount and later usage via fhcBase plugin
setDependencies(deps) {
Object.assign(this.deps, deps);
if (this.deps.$fhcAlert && this.deps.$p) this.resolveReady();
}
getUri(url) {
return this.axiosInstance.getUri({ url });
}
get(form, uri, params, config) {
[uri, params, config] = this.get_config(form, uri, params, config);
if (params) {
config = config ? { ...config, params } : { params };
}
return this.axiosInstance.get(uri, config);
}
post(form, uri, data, config) {
[uri, data, config] = this.get_config(form, uri, data, config);
return this.axiosInstance.post(uri, data, config);
}
getErrorHandler(config) {
return this.get_error_handler(config);
}
async call(factory, configoverwrite, form) {
if (Array.isArray(factory)) {
return Promise.allSettled(factory.map((config, index) => {
if (!Array.isArray(config)) config = ['#' + index, config];
return this.call(config[1], { errorHeader: config[0], errorHandling: false });
})).then(result => {
const [ , , config ] = this.get_config(form, undefined, undefined, configoverwrite || {});
const errorConfig = this.get_error_handler(config);
if (!errorConfig.success && !errorConfig.fail) {
return result;
}
const typedErrors = {};
for (let res of result) {
const [ allowed, item ] = res.status === 'fulfilled'
? [ errorConfig.success, res.value ]
: [ errorConfig.fail, res.reason ];
if (!allowed)
return;
const errors = this.popHandleableErrors(errorConfig, this.get_error_list(item));
for (let type in errors) {
if (!typedErrors[type])
typedErrors[type] = {
[item.config.errorHeader]: errors[type]
};
else
typedErrors[type][item.config.errorHeader] = errors[type];
}
};
for (let errType in typedErrors) {
errorConfig.handler[errType](typedErrors[errType]);
}
return result;
});
}
let { method = 'get', url, params, config } = factory;
if (configoverwrite !== undefined) config = configoverwrite;
method = method.toLowerCase();
return method === 'post' ? this.post(form, url, params, config) : this.get(form, url, params, config);
}
// === public functions END ===
// === private functions START ===
setupInterceptors() {
this.axiosInstance.interceptors.request.use(config => {
if (config.method != 'post' || !config.data)
return config;
if (config.data instanceof FormData)
return config;
if (!Object.values(config.data).every(item => {
if (item instanceof FileList)
return false;
if (Array.isArray(item))
return item.every(i => !(i instanceof File));
return true;
})) {
const newData = Object.entries(config.data).reduce((nd, [key, item]) => {
if (item instanceof FileList) {
for (const file of item)
nd.FormData.append(key + (item.length > 1 ? '[]' : ''), file);
} else if (Array.isArray(item)) {
if (item.every(i => !(i instanceof File))) {
nd.jsondata[key] = item;
} else {
item.forEach(file => nd.FormData.append(key + (item.length > 1 ? '[]' : ''), file));
}
} else {
nd.jsondata[key] = item;
}
return nd;
}, {
FormData: new FormData(),
jsondata: {}
});
newData.FormData.append('_jsondata', JSON.stringify(newData.jsondata));
config.data = newData.FormData;
}
return config;
});
this.axiosInstance.interceptors.response.use(
response => {
if (response.config?.errorHandling == 'off'
|| response.config?.errorHandling === false
|| response.config?.errorHandling == 'fail')
return this.clean_return_value(response);
// NOTE(chris): loop through errors
if (response.data.errors)
response.data.errors = response.data.errors.filter(
err => (response.config[err.type + 'ErrorHandler'] || this.DEFAULT_ERROR_CONFIG.handler[err.type])(err, response.config)
);
return this.clean_return_value(response);
},
error => {
if (error.code == 'ERR_CANCELED')
return Promise.reject({ handled: true, ...error });
const errorConfig = this.get_error_handler(error.config);
if (!errorConfig.fail)
return Promise.reject(error);
const remaining = this.get_error_list(error);
const errors = this.popHandleableErrors(errorConfig, remaining);
for (let type in errors) {
errorConfig.handler[type](errors[type]);
}
if (remaining.length)
return Promise.reject(error);
return Promise.reject({ handled: true, ...error });
}
);
}
setupDefaultConfig() {
this.DEFAULT_ERROR_CONFIG = {
success: true,
fail: true,
combine: {
form: ['validation', 'general'],
toast: ['validation', 'general', 'not_found', 'site_failed']
},
handler: {
form: (form, errors) => {
form.clearValidation();
errors.forEach(err => form.setFeedback(
false,
err.messages || err.message
));
},
toast: async (errors) => {
await this.ready;
async function _format_toast(errors) {
errors = errors.reduce((result, err) => {
switch (err.type) {
case 'not_found':
case 'site_failed':
if (err.message)
result[err.message] = [err.url];
else
result._default = [err.url];
break;
case 'general':
if (!result._default)
result._default = [];
result._default.push(err.message);
break;
case 'validation':
Object.entries(err.messages)
.forEach(([field, msg]) => {
if (!result[field])
result[field] = [];
if (Array.isArray(msg))
result[field].push(...msg);
else
result[field].push(msg);
});
break;
}
return result;
}, {});
let counter = 0;
const msgs = await Promise.all(Object.entries(errors)
.sort((a, b) => ['_default'].indexOf(b[0]) - ['_default'].indexOf(a[0])) // sort _default first
.map(async ([field, msgs]) => {
if (field == '_default') {
await this.deps.$p.loadCategory('dashboard');
const general = this.deps.$p.t('dashboard/general');
field = '<dt class="d-none">' + general + '</dt>';
} else {
field = '<dt>' + field + '</dt>';
}
counter += msgs.length;
return field
+ '<dd>'
+ msgs.join('</dd><dd>')
+ '</dd>';
}));
return {
counter,
msgs
}
}
let counter, msgs;
if (Array.isArray(errors)) {
({ counter, msgs } = await _format_toast(errors));
} else {
({ counter, msgs } = await Object.entries(errors)
.reduce(async (res, [title, errs]) => {
const result = await res;
const { counter, msgs } = await _format_toast(errs);
result.counter += counter;
result.msgs.push('<dt>'
+ title
+ '</dt><dd><dl>'
+ msgs.join('')
+ '</dl></dd>');
return result;
}, Promise.resolve({ counter: 0, msgs: []})));
}
await this.deps.$p.loadCategory('ui');
const n_errors = this.deps.$p.t('ui/n_errors', { n: counter });
this.deps.$fhcAlert.alertDefault(
'error',
n_errors,
'<dl>' + msgs.join('') + '</dl>',
true,
true
);
},
php: async (errors) => {
await this.ready;
this._send_array_or_object(errors, (error, title) => {
var message = '';
message += 'Message: ' + error.message + '\n\n';
message += 'Filename: ' + error.filename + '\n';
message += 'Line Number: ' + error.line + '\n';
if (error.backtrace && error.backtrace.length) {
message += '\nBacktrace: ';
error.backtrace.forEach(err => {
message += '\n\tFile: ' + err.file + '\n';
message += '\tLine: ' + err.line + '\n';
message += '\tFunction: ' + err.function + '\n';
});
}
switch (error.severity) {
case 'Warning':
case 'Core Warning':
case 'Compile Warning':
case 'User Warning':
if (title)
title += ': PHP ' + error.severity;
else
title = 'PHP ' + error.severity;
this.deps.$fhcAlert.alertDefault('warn', title, message, true);
break;
case 'Notice':
case 'User Notice':
case 'Runtime Notice':
if (title)
title += ': PHP ' + error.severity;
else
title = 'PHP ' + error.severity;
this.deps.$fhcAlert.alertDefault('info', title, message, true);
break;
default:
message = 'Type: PHP ' + error.severity + '\n\n' + message;
if (title)
message = title + '\n\n' + message;
this.deps.$fhcAlert.alertSystemError(message);
break;
}
});
},
exception: async (errors) => {
await this.ready;
this._send_array_or_object(errors, (error, title) => {
var message = '';
if (title)
message += title + '\n\n';
message += 'Type: ' + error.class + '\n\n';
message += 'Message: ' + error.message + '\n\n';
message += 'Filename: ' + error.filename + '\n';
message += 'Line Number: ' + error.line + '\n';
if (error.backtrace && error.backtrace.length) {
message += '\nBacktrace: ';
error.backtrace.forEach(err => {
message += '\n\tFile: ' + err.file + '\n';
message += '\tLine: ' + err.line + '\n';
message += '\tFunction: ' + err.function + '\n';
});
}
this.deps.$fhcAlert.alertSystemError(message);
});
},
db: async (errors) => {
await this.ready;
this._send_array_or_object(errors, (error, title) => {
var message = '';
if (title)
message += title + '\n\n';
if (error.heading !== undefined)
message += error.heading + '\n\n';
if (error.code !== undefined)
message += 'Code: ' + error.code + '\n\n';
if (error.sql !== undefined)
message += 'SQL: ' + error.sql + '\n\n';
if (error.message !== undefined)
message += 'Message: ' + error.message + '\n\n';
else if (error.messages !== undefined)
message += 'Messages: ' + error.messages.join('\n\t') + '\n\n';
if (error.filename !== undefined)
message += 'Filename: ' + error.filename + '\n';
if (error.line !== undefined)
message += 'Line Number: ' + error.line + '\n';
this.deps.$fhcAlert.alertSystemError(message);
});
},
auth: async (errors) => {
await this.ready;
this._send_array_or_object(errors, (error, title) => {
if (title)
title += ': ' + error.message;
else
title = error.message;
var message = '';
message += 'Controller name: ' + error.controller + '\n';
message += 'Method name: ' + error.method + '\n';
message += 'Required permissions: ' + error.required_permissions;
this.deps.$fhcAlert.alertDefault(
'error',
title,
message,
true
);
});
}
}
};
}
_send_array_or_object(errors, func) {
if (!errors) return;
if (Array.isArray(errors)) {
errors.forEach(error => func(error));
return;
}
// Handle Single Error Object
if (errors.type) {
func(errors);
return;
}
// Handle Category Container
Object.entries(errors).forEach(([title, value]) => {
const errorList = Array.isArray(value) ? value : [value];
errorList.forEach(error => {
if (error && typeof error === 'object') {
func(error, title);
}
});
});
}
get_config(form, uri, data, config) {
if (typeof form == 'string' && config === undefined) {
[uri, data, config] = [form, uri, data];
form = undefined;
} else if (form) {
if (typeof form != 'object')
throw new TypeError('Parameter 1 of _get_config must be an object or a string');
if (uri === undefined && data === undefined && config === undefined) {
config = form;
form = undefined;
}
}
if (form) {
// NOTE(chris): check if form is fhc-form
if (!form.clearValidation || !form.setFeedback)
throw new TypeError("'form' is not a Form Component");
form = {
clearValidation: form.clearValidation,
setFeedback: form.setFeedback
};
if (config)
config.form = form;
else
config = {form};
}
return [uri, data, config];
}
clean_return_value(response) {
if (typeof response.data === 'string' || response.data instanceof String)
return this.clean_return_value({ data: response });
const result = response.data;
delete response.data;
if (!result)
return {meta: {response}, data: null};
if (!result.meta)
result.meta = { response };
else
result.meta.response = response;
return result;
}
merge_error_config(config) {
if (config === false || config === 'off')
return { ...this.DEFAULT_ERROR_CONFIG, success: false, fail: false };
if (!config || config === true)
return { ...this.DEFAULT_ERROR_CONFIG };
if (config === 'success')
return { ...this.DEFAULT_ERROR_CONFIG, fail: false };
if (config === 'fail')
return { ...this.DEFAULT_ERROR_CONFIG, success: false };
const { success, fail, handler, combine } = config;
config = { ...this.DEFAULT_ERROR_CONFIG };
Object.entries({ fail, success }).forEach(([key, value]) => {
if (value !== undefined)
config[key] = value;
});
Object.entries({ handler, combine }).forEach(([key, value]) => {
if (value !== undefined)
config[key] = { ...config[key], ...value };
});
return config;
}
get_error_handler(config) {
const result = this.merge_error_config(config?.errorHandling);
if (!config?.form) {
result.combine = { ...result.combine, form: [] };
} else {
const formHandler = result.handler.form;
result.handler = { ...result.handler, form: errors => formHandler(config.form, errors) };
}
return result;
}
get_error_list(error) {
if (error.response) {
if (error.response.status == 404) {
return [{
type: 'not_found',
message: error.message,
url: error.request.responseURL
}];
} else {
if (error.response.data.errors == undefined) return [];
return error.response.data.errors;
}
} else if (error.request) {
return [{
type: 'site_failed',
message: error.message,
url: error.request.responseURL
}];
} else {
return [{
type: 'script',
message: error.message
}];
}
}
popHandleableErrors(errorHandling, errors) {
const result = {};
const copy = [];
if (errors == undefined) return {};
while (errors.length)
copy.push(errors.pop());
for (let error of copy) {
let type = error.type;
let newType = null;
for (let t in errorHandling.combine) {
let newTypeCombinesType = errorHandling
.combine[t]
.includes(type);
let newTypeHasHandler = errorHandling.handler[t];
if (newTypeCombinesType && newTypeHasHandler) {
newType = t;
if (newType == 'form')
break;
}
}
if (newType)
type = newType;
const handler = errorHandling.handler[type];
if (handler) {
if (!result[type])
result[type] = [];
if (Array.isArray(error))
result[type].push(...error);
else
result[type].push(error);
continue;
}
errors.push(error);
}
return result;
}
// === private functions END ===
}
export default BaseApi;
+100
View File
@@ -0,0 +1,100 @@
import ApiPhrasen from '../../api/factory/phrasen.js';
const categories = Vue.reactive({});
const loadingModules = {};
let user_language = Vue.ref(FHC_JS_DATA_STORAGE_OBJECT.user_language);
export const user_locale = Vue.computed(() => {
if (!user_language.value) return null;
return FHC_JS_DATA_STORAGE_OBJECT.server_languages.find(l => l.sprache == user_language.value).LC_Time;
});
function extractCategory(obj, category) {
return obj.filter(e => e.category == category).reduce((res, elem) => {
if (!res[elem.phrase]) res[elem.phrase] = elem.text;
return res;
}, {});
}
function getValueForLoadedPhrase(category, phrase, params) {
let result = categories[category][phrase];
if (!result)
return '<< PHRASE ' + phrase + '>>';
if (params)
result = result.replace(/\{([^}]*)\}/g, (match, p1) => params[p1] === undefined ? match : params[p1]);
return result;
}
export default {
init(app) {
// Create a controller to resolve the promise later
let resolveReady;
const readyPromise = new Promise(resolve => { resolveReady = resolve; });
const $p = {
deps: {},
ready: readyPromise,
setDeps(deps) {
Object.assign(this.deps, deps);
// Once we have the API, we are ready to load data
if (this.deps.$api) resolveReady();
},
user_language,
user_locale,
async loadCategory(category) {
if (Array.isArray(category))
return Promise.all(category.map(cat => this.loadCategory(cat)));
// 2. SAFETY: Check if API is available via deps
await this.ready;
if (!loadingModules[category])
loadingModules[category] = this.deps.$api
.call(ApiPhrasen.loadCategory(category))
.then(res => res?.data ? extractCategory(res.data, category) : {})
.then(res => {
categories[category] = res;
});
return loadingModules[category];
},
t_ref(category, phrase, params) {
console.warn('deprecated');
return Vue.computed(() => this.t(category, phrase, params));
},
t(category, phrase, params) {
if (params === undefined && (
(Array.isArray(category) && category.length == 2) ||
(category.split && category.split('/').length == 2))
) {
params = phrase;
[category, phrase] = category.split ? category.split('/') : category;
}
if (phrase === undefined) {
console.error('invalid input', category, phrase, params);
return '';
}
let val = Vue.computed(() => {
if (!categories[category])
return '';
return getValueForLoadedPhrase(category, phrase, params);
});
if (!categories[category])
this.loadCategory(category);
return val.value;
},
async setLanguage(language) {
await this.ready;
const catArray = Object.keys(categories);
return this.deps.$api.call(ApiPhrasen.setLanguage(catArray, language)).then(res => {
res.data.forEach(row => { categories[row.category][row.phrase] = row.text; });
user_language.value = language;
return res;
});
}
};
return $p;
}
};
+37
View File
@@ -0,0 +1,37 @@
import Phrasen from './BasePhrasen.js';
import Alert from './BaseAlert.js';
import { BaseApi } from './BaseApi.js';
export default {
install(app, options = {}) {
// init in order
const $p = Phrasen.init(app);
const $fhcAlert = Alert.init(app, $p);
// try to reuse existing CoreRESTClient api instance if one has been active since before
// fhcBase Plugin install
let $api = app.config.globalProperties.$api;
if (!($api instanceof BaseApi)) {
$api = new BaseApi({ $fhcAlert, $p }, options);
} else {
// If api existed pre-app install
$api.setDependencies({ $fhcAlert, $p });
}
// set ready promise for awaiting async functions
$p.setDeps({ $api, $fhcAlert });
$fhcAlert.setDeps({ $api });
// globalProperties Binding & provide
app.config.globalProperties.$p = $p;
app.config.globalProperties.$fhcAlert = $fhcAlert;
app.config.globalProperties.$api = $api;
app.config.globalProperties.$fhcApi = $api;
app.provide('$api', $api);
app.provide('$fhcApi', $api);
app.provide('$p', $p);
app.provide('$fhcAlert', $fhcAlert);
}
};