Compare commits

..

11 Commits

11 changed files with 1401 additions and 422 deletions
@@ -0,0 +1,36 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
/**
*
*/
class ProjektabgabeUebersicht extends Auth_Controller
{
/**
* Constructor
*/
public function __construct()
{
parent::__construct([
'index' => ['basis/cis:r']
]);
}
// -----------------------------------------------------------------------------------------------------------------
// Public methods
/**
* @return void
*/
public function index()
{
// TODO create permission
$viewData = array(
'uid' => getAuthUID(),
'showEdit' => true
);
$this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'ProjektabgabeUebersicht']);
}
}
@@ -0,0 +1,206 @@
<?php
/**
* FH-Complete
*
* @package FHC-API
* @author FHC-Team
* @copyright Copyright (c) 2016, fhcomplete.org
* @license GPLv3
* @link http://fhcomplete.org
* @since Version 1.0
* @filesource
*/
// ------------------------------------------------------------------------
if (!defined('BASEPATH')) exit('No direct script access allowed');
use CI3_Events as Events;
class PaabgabeUebersicht extends FHCAPI_Controller
{
const DOWNLOAD_PERMISSION = 'lehre/abgabetool:download';
const ABGABE_TYPES = ['Bachelor', 'Diplom'];
/**
* PaabgabeUebersicht API constructor.
*/
public function __construct()
{
parent::__construct([
'getPaAbgaben' => array('lehre/abgabetool:r'),
'getStudiengaenge' => array('lehre/abgabetool:r'),
'getTermine' => array('lehre/abgabetool:r'),
'getPaAbgabetypen' => array('lehre/abgabetool:r'),
'downloadZip' => array('lehre/abgabetool:r')
//'downloadProjektarbeit' => array('lehre/abgabetool:r')
]);
$this->load->model('education/Paabgabe_model', 'PaabgabeModel');
$this->load->library('PermissionLib');
// Load Phrases
$this->loadPhrases([
'abgabetool'
]);
}
/**
* Get Projektabgaben for search criteria.
*/
public function getPaAbgaben()
{
$studiengang_kz = $this->input->get('studiengang_kz');
$abgabetyp_kurzbz = $this->input->get('abgabetyp_kurzbz');
$abgabedatum = $this->input->get('abgabedatum');
$personSearchString = $this->input->get('personSearchString');
$result = $this->PaabgabeModel->getPaAbgaben(self::ABGABE_TYPES, $studiengang_kz, $abgabetyp_kurzbz, $abgabedatum, $personSearchString);
if (isError($result)) $this->terminateWithError(getError($result), self::ERROR_TYPE_DB);
// check wether Abgabe is in visual library
if (hasData($result))
{
Events::trigger('in_visual_library', getData($result));
}
$this->terminateWithSuccess(getData($result) ?: []);
}
/**
* Get all Studiengänge for which user is entitled for
*/
public function getStudiengaenge()
{
$studiengang_kz_arr = $this->permissionlib->getSTG_isEntitledFor(self::DOWNLOAD_PERMISSION);
if (!$studiengang_kz_arr) $this->terminateWithSuccess([]);
$this->load->model('organisation/Studiengang_model', 'StudiengangModel');
$this->StudiengangModel->addSelect('tbl_studiengang.*, UPPER(tbl_studiengang.typ || tbl_studiengang.kurzbz) AS kuerzel', $studiengang_kz_arr);
$this->StudiengangModel->db->where_in('studiengang_kz', $studiengang_kz_arr);
$this->StudiengangModel->addOrder('typ, kurzbz');
$result = $this->StudiengangModel->load();
if (isError($result)) $this->terminateWithError(getError($result), self::ERROR_TYPE_DB);
$this->terminateWithSuccess((getData($result) ?: []));
}
/**
* Get projekt work due dates, depending on search criteria.
*/
public function getTermine()
{
$studiengang_kz = $this->input->get('studiengang_kz');
$abgabetyp_kurzbz = $this->input->get('abgabetyp_kurzbz');
$result = $this->PaabgabeModel->getTermine(self::ABGABE_TYPES, $studiengang_kz, $abgabetyp_kurzbz);
if (isError($result)) $this->terminateWithError(getError($result), self::ERROR_TYPE_DB);
$this->terminateWithSuccess((getData($result) ?: []));
}
/**
* Get all submission types.
*/
public function getPaAbgabetypen()
{
// Load model PaabgabetypModel
$this->load->model('education/Paabgabetyp_model', 'PaabgabetypModel');
$this->PaabgabetypModel->addOrder('bezeichnung');
$result = $this->PaabgabetypModel->load();
if (isError($result)) $this->terminateWithError(getError($result), self::ERROR_TYPE_DB);
$this->terminateWithSuccess((getData($result) ?: []));
}
/**
* Download zip files with project works matching submission search criteria.
*/
public function downloadZip()
{
$studiengang_kz = $this->input->get('studiengang_kz');
$abgabetyp_kurzbz = $this->input->get('abgabetyp_kurzbz');
$abgabedatum = $this->input->get('abgabedatum');
$personSearchString = $this->input->get('personSearchString');
if (!isset($studiengang_kz) && !isset($abgabetyp_kurzbz) && !isset($abgabedatum) && !isset($personSearchString))
$this->terminateWithFileOutput('text/plain', $this->p->t('abgabetool', 'nichtsAusgewaehlt'));
$this->load->library('zip');
$result = $this->PaabgabeModel->getPaAbgaben(self::ABGABE_TYPES, $studiengang_kz, $abgabetyp_kurzbz, $abgabedatum, $personSearchString);
if (isError($result)) $this->terminateWithFileOutput('text/plain', getError($result));
$fileExists = false;
$studiengang_kuerzel = null;
if (!hasData($result)) $this->terminateWithFileOutput('text/plain', $this->p->t('abgabetool', 'keineDateienVorhanden'));
$abgaben = getData($result);
foreach ($abgaben as $abgabe)
{
$path = PAABGABE_PATH.$abgabe->paabgabe_id.'_'.$abgabe->uid.'.pdf';
if (file_exists($path))
{
$fileExists = true;
$studiengang_kuerzel = $abgabe->studiengang_kuerzel;
$this->zip->read_file($path);
}
}
if (!$fileExists) $this->terminateWithFileOutput('text/plain', $this->p->t('abgabetool', 'keineDateienVorhanden'));
$studiengang_kz = $this->input->get('studiengang_kz');
$zipFileName = 'Abgabe'.(isset($studiengang_kz) && isset($studiengang_kuerzel) ? '_'.$studiengang_kuerzel : '').'.zip';
$this->zip->download($zipFileName);
}
/**
* Download Projektarbeit document.
*/
//~ public function downloadProjektarbeit()
//~ {
//~ $paabgabe_id = $this->input->get('paabgabe_id');
//~ if (!is_numeric($paabgabe_id))
//~ $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id' => 'Abgabe ID']), self::ERROR_TYPE_GENERAL);
//~ //$abgabeRes = $this->PaabgabeModel->getEndabgabe($projektarbeit_id);
//~ $this->PaabgabeModel->addSelect("paabgabe_id, student_uid, tbl_paabgabe.datum, tbl_paabgabe.abgabedatum, projekttyp_kurzbz, titel, titel_english,
//~ paabgabe_id || '_' || student_uid || '.pdf' AS filename");
//~ $this->PaabgabeModel->addJoin('lehre.tbl_projektarbeit', 'projektarbeit_id');
//~ $abgabeRes = $this->PaabgabeModel->load($paabgabe_id);
//~ if (isError($abgabeRes))
//~ show_error(getError($abgabeRes));
//~ if (hasData($abgabeRes))
//~ {
//~ $endabgabe = getData($abgabeRes)[0];
//~ $filepath = PAABGABE_PATH.$endabgabe->filename;
//~ if (file_exists($filepath))
//~ {
//~ $this->output
//~ ->set_status_header(200)
//~ ->set_content_type('application/pdf', 'utf-8')
//~ ->set_header('Content-Disposition: attachment; filename="'.$endabgabe->filename.'"')
//~ ->set_output(file_get_contents($filepath))
//~ ->_display();
//~ }
//~ else
//~ {
//~ show_error("File does not exist.");
//~ }
//~ }
//~ }
}
@@ -61,6 +61,174 @@ class Paabgabe_model extends DB_Model
return $this->execReadOnlyQuery($qry, array($person_id));
}
/**
* Gets project submissions for search criteria.
* @param array $projekttyp_kurzbz_arr contains all relevant project types (e.g. Bachelor)
* @param int $studiengang_kz study program
* @param string $abgabetyp_kurzbz project submission type (e.g. end upload, intermediate submission)
* @param string $abgabedatum due date for hand-in
* @param string $personSearchString for searching by person, i.e. name, uid, person/prestudent id
* @param int $limit limiting max number of results if search criteria is not precise enough
* @return object
*/
public function getPaAbgaben(
$projekttyp_kurzbz_arr,
$studiengang_kz = null,
$abgabetyp_kurzbz = null,
$abgabedatum = null,
$personSearchString = null,
$limit = 1000
) {
$params = [];
$qry = "
SELECT
stg.bezeichnung AS stgbez, paabg.datum AS termin,
paabg.paabgabe_id, paabg.projektarbeit_id, paabg.paabgabetyp_kurzbz, paabg.abgabedatum,
abgabetyp.bezeichnung AS paabgabetyp_bezeichnung, ben.uid, pers.vorname, pers.nachname, pa.projekttyp_kurzbz, pa.titel,
UPPER(stg.typ || stg.kurzbz) AS studiengang_kuerzel,
(
/* show all relevant Studiengänge of person and wether it is an employee*/
SELECT
STRING_AGG(studiengang || ' ' || last_status, ' | ')
|| (CASE WHEN EXISTS (
SELECT 1 FROM public.tbl_mitarbeiter ma
JOIN public.tbl_benutzer ben ON ma.mitarbeiter_uid = ben.uid
WHERE person_id = prestudents.person_id
AND ben.aktiv
) THEN ' | Mitarbeiter' ELSE '' END)
FROM (
SELECT
DISTINCT person_id, prestudent_id, UPPER(stg.typ || stg.kurzbz) AS studiengang,
get_rolle_prestudent(ps.prestudent_id, null) AS last_status
FROM
public.tbl_prestudent ps
JOIN public.tbl_studiengang stg USING (studiengang_kz)
WHERE
person_id = pers.person_id
ORDER BY
prestudent_id DESC
) prestudents
WHERE
last_status IN ('Abgewiesener','Aufgenommener', 'Student', 'Incoming', 'Diplomand', 'Abbrecher', 'Unterbrecher', 'Absolvent')
GROUP BY
person_id
LIMIT 1;
) AS status
FROM
lehre.tbl_projektarbeit pa
JOIN campus.tbl_paabgabe paabg USING(projektarbeit_id)
JOIN campus.tbl_paabgabetyp abgabetyp USING(paabgabetyp_kurzbz)
LEFT JOIN public.tbl_benutzer ben ON(uid=student_uid)
LEFT JOIN public.tbl_person pers ON(ben.person_id=pers.person_id)
LEFT JOIN lehre.tbl_lehreinheit USING(lehreinheit_id)
LEFT JOIN lehre.tbl_lehrveranstaltung USING(lehrveranstaltung_id)
LEFT JOIN public.tbl_studiengang stg USING(studiengang_kz)
WHERE
TRUE";
if (isset($projekttyp_kurzbz_arr) && !isEmptyArray($projekttyp_kurzbz_arr))
{
$qry .= " AND projekttyp_kurzbz IN ?";
$params[] = $projekttyp_kurzbz_arr;
}
if (isset($studiengang_kz) && is_numeric($studiengang_kz))
{
$qry .= " AND stg.studiengang_kz=?";
$params[] = $studiengang_kz;
}
if (isset($abgabetyp_kurzbz))
{
$qry .= " AND paabg.paabgabetyp_kurzbz=?";
$params[] = $abgabetyp_kurzbz;
}
if (isset($abgabedatum))
{
$qry .= " AND paabg.datum=?";
$params[] = $abgabedatum;
}
if (is_numeric($personSearchString))
{
$personSearchString = (int) $personSearchString;
$params = array_merge($params, [$personSearchString, $personSearchString]);
$qry .= " AND (
pers.person_id = ?
OR EXISTS (SELECT 1 FROM public.tbl_prestudent WHERE person_id = pers.person_id AND prestudent_id = ?)
)";
}
elseif (is_string($personSearchString))
{
// remove empty spaces and lowercase
$personSearchString = strtolower(str_replace(' ', '', $personSearchString));
$qry .= " AND (
LOWER(REPLACE(pers.nachname || pers.vorname || pers.nachname, ' ', '')) LIKE ".$this->db->escape('%'.$personSearchString.'%')."
OR ben.uid LIKE ".$this->db->escape('%'.$personSearchString.'%')."
)";
}
$qry .= " ORDER BY nachname";
if (isset($limit) && is_numeric($limit) && (!isset($studiengang_kz) || !is_numeric($studiengang_kz)) && !isset($abgabedatum))
{
$qry .= " LIMIT ?";
$params[] = $limit;
}
return $this->execReadOnlyQuery($qry, $params);
}
/**
* Gets due dates for projekt submission search criteria.
* @param array $projekttyp_kurzbz_arr contains all relevant project types (e.g. Bachelor)
* @param int $studiengang_kz study program
* @param string $abgabetyp_kurzbz project submission type (e.g. end upload, intermediate submission)
* @return object
*/
public function getTermine($projekttyp_kurzbz_arr, $studiengang_kz, $abgabetyp_kurzbz)
{
$params = [];
$qry = "
SELECT
DISTINCT tbl_paabgabe.datum as termin, to_char(tbl_paabgabe.datum, 'DD.MM.YYYY') as termin_anzeige
FROM
lehre.tbl_projektarbeit
JOIN campus.tbl_paabgabe USING(projektarbeit_id)
LEFT JOIN public.tbl_benutzer ON(uid=student_uid)
LEFT JOIN public.tbl_person ON(tbl_benutzer.person_id=tbl_person.person_id)
LEFT JOIN lehre.tbl_lehreinheit USING(lehreinheit_id)
LEFT JOIN lehre.tbl_lehrveranstaltung USING(lehrveranstaltung_id)
LEFT JOIN public.tbl_studiengang USING(studiengang_kz)
WHERE
TRUE";
if (isset($projekttyp_kurzbz_arr) && !isEmptyArray($projekttyp_kurzbz_arr))
{
$qry .= " AND projekttyp_kurzbz IN ?";
$params[] = $projekttyp_kurzbz_arr;
}
if (isset($studiengang_kz) && is_numeric($studiengang_kz))
{
$qry .= " AND public.tbl_studiengang.studiengang_kz=?";
$params[] = $studiengang_kz;
}
if (isset($abgabetyp_kurzbz))
{
$qry .= " AND campus.tbl_paabgabe.paabgabetyp_kurzbz=?";
$params[] = $abgabetyp_kurzbz;
}
$qry .= " ORDER BY termin DESC";
return $this->execReadOnlyQuery($qry, $params);
}
public function findAbgabenNewOrUpdatedSince($interval, $relevantTypes)
{
@@ -594,7 +594,10 @@ class Studiengang_model extends DB_Model
$this->addSelect('p.prestudent_id');
$this->addSelect('pers.vorname');
$this->addSelect('pers.nachname');
$this->addSelect("CONCAT(UPPER(pers.nachname), ' ', pers.vorname, ' (', " . $this->dbTable . ".bezeichnung, ')') AS name");
$this->addSelect("CONCAT(UPPER(pers.nachname), ' ', pers.vorname, ' (', "
. $this->dbTable . ".bezeichnung, ', ', "
. "UPPER(" . $this->dbTable . ".typ), "
. "UPPER(" . $this->dbTable . ".kurzbz),')') AS name");
$this->addJoin('public.tbl_prestudent p', 'studiengang_kz');
$this->addJoin(
@@ -0,0 +1,30 @@
export default {
getPaAbgaben(studiengang_kz, abgabetyp_kurzbz, abgabedatum, personSearchString) {
return {
method: 'get',
url: '/api/frontend/v1/education/PaabgabeUebersicht/getPaAbgaben',
params: {
studiengang_kz: studiengang_kz, abgabetyp_kurzbz: abgabetyp_kurzbz, abgabedatum: abgabedatum, personSearchString: personSearchString
}
};
},
getStudiengaenge() {
return {
method: 'get',
url: '/api/frontend/v1/education/PaabgabeUebersicht/getStudiengaenge'
};
},
getTermine(studiengang_kz, abgabetyp_kurzbz) {
return {
method: 'get',
url: '/api/frontend/v1/education/PaabgabeUebersicht/getTermine',
params: { studiengang_kz: studiengang_kz, abgabetyp_kurzbz: abgabetyp_kurzbz }
};
},
getPaAbgabetypen() {
return {
method: 'get',
url: '/api/frontend/v1/education/PaabgabeUebersicht/getPaAbgabetypen'
};
}
};
-12
View File
@@ -136,18 +136,6 @@ const app = Vue.createApp({
}
};
},
computed: {
isMobile() {
const smallScreen = window.matchMedia("(max-width: 767px)").matches;
const touchCapable = ("ontouchstart" in window) || navigator.maxTouchPoints > 0;
return smallScreen;// && touchCapable;
},
},
provide() {
return {
isMobile: this.isMobile
}
},
methods: {
searchfunction: function(searchsettings) {
return this.$api.call(ApiSearchbar.searchCis(searchsettings));
+19 -12
View File
@@ -17,6 +17,7 @@ import AbgabetoolMitarbeiter from "../../components/Cis/Abgabetool/AbgabetoolMit
import AbgabetoolAssistenz from "../../components/Cis/Abgabetool/AbgabetoolAssistenz.js";
import DeadlineOverview from "../../components/Cis/Abgabetool/DeadlineOverview.js";
import Studium from "../../components/Cis/Studium/Studium.js";
import PaabgabeUebersicht from "../../components/Cis/ProjektabgabeUebersicht/ProjektabgabeUebersicht.js";
import ApiRenderers from '../../api/factory/renderers.js';
import ApiRouteInfo from '../../api/factory/routeinfo.js';
@@ -75,6 +76,12 @@ const router = VueRouter.createRouter({
component: Raumsuche,
props: true
},
{
path: `/Cis/ProjektabgabeUebersicht`,
name: 'PaabgabeUebersicht',
component: PaabgabeUebersicht,
props: true
},
// Redirect old links to new format
{
path: "/CisVue/Cms/getRoomInformation/:ort_kurzbz",
@@ -161,7 +168,7 @@ const router = VueRouter.createRouter({
// Redirect old links to new format
{
// only trigger on first param being numeric to avoid paths like "LvPlan/Month" entering here
path: "/Cis/LvPlan/:lv_id(\\d+)",
path: "/Cis/LvPlan/:lv_id(\\d+)",
name: "LvPlanOld",
component: LvPlan,
redirect(to) {
@@ -238,13 +245,13 @@ const app = Vue.createApp({
const smallScreen = window.matchMedia("(max-width: 767px)").matches;
const touchCapable = ("ontouchstart" in window) || navigator.maxTouchPoints > 0;
return smallScreen;// && touchCapable;
},
}
},
provide() {
return { // provide injectable & watchable language property
language: Vue.computed(() => this.$p.user_language),
renderers: Vue.computed(() => this.renderers),
isMobile: this.isMobile,
isMobile: this.isMobile
}
},
methods: {
@@ -258,7 +265,7 @@ const app = Vue.createApp({
if(target?.id == 'skiplink') return
if (target && this.isInternalRoute(target.href)) {
const url = new URL(target.href)
const path = url.pathname
const base = this.$router.options.history.base
const route = path.replace(base, '') || '/'
@@ -266,19 +273,19 @@ const app = Vue.createApp({
// let click event propagate normally if we dont route internally
const res = this.$router.resolve(route)
if(!res?.matched?.length || res.name === 'Fallback') return
event.preventDefault(); // Prevent browser navigation
if(this.isMobile) { // toggle the menu
const navMain = document.getElementById('nav-main');
// fix unwanted toggle from off to on for some links on mobile
if(navMain.classList.contains('show')){
document.getElementById('nav-main-btn').click();
}
}
}
this.$router.push(route);
}
}
},
@@ -294,9 +301,9 @@ const app = Vue.createApp({
let calendarEvent = null;
if (data[rendertype].modalTitle)
modalTitle = Vue.markRaw(Vue.defineAsyncComponent(() => import(data[rendertype].modalTitle)));
if (data[rendertype].modalContent)
if (data[rendertype].modalContent)
modalContent = Vue.markRaw(Vue.defineAsyncComponent(() => import(data[rendertype].modalContent)));
if (data[rendertype].calendarEvent)
if (data[rendertype].calendarEvent)
calendarEvent = Vue.markRaw(Vue.defineAsyncComponent(() => import(data[rendertype].calendarEvent)));
if (data[rendertype].calendarEventStyles){
@@ -353,4 +360,4 @@ app.mount('#fhccontent');
router.afterEach((to, from, failure) => {
app.config.globalProperties.$api.call(ApiRouteInfo.info('cis4', to.fullPath));
});
});
+20 -47
View File
@@ -30,7 +30,6 @@ export default {
menuOpen:true,
};
},
inject: ["isMobile"],
provide(){
return{
setActiveEntry: this.setActiveEntry,
@@ -59,7 +58,7 @@ export default {
},
site_url(){
return FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
},
}
},
methods: {
fetchMenu() {
@@ -113,26 +112,10 @@ export default {
});
},
template: /*html*/`
<div
id="header-options-collapsible"
class="collapse multi-collapse collapse-horizontal show"
>
<div class="d-flex flex-row align-items-center gap-2 h-100" style="width: 79px">
<button id="nav-main-btn" class="navbar-toggler rounded-0 px-2 border-0" type="button" data-bs-toggle="offcanvas" data-bs-target="#nav-main" aria-controls="nav-main" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<span v-if="isMobile" class="d-flex flex-row align-items-center">
<theme-switch></theme-switch>
</span>
</div>
</div>
<fhc-searchbar
:searchoptions="searchbaroptions"
:searchfunction="searchfunction"
ref="searchbar"
id="nav-search"
class="fhc-searchbar w-100 py-1 py-lg-2"
></fhc-searchbar>
<button id="nav-main-btn" class="navbar-toggler rounded-0" type="button" data-bs-toggle="offcanvas" data-bs-target="#nav-main" aria-controls="nav-main" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<fhc-searchbar ref="searchbar" id="nav-search" class="fhc-searchbar w-100 py-1 py-lg-2" :searchoptions="searchbaroptions" :searchfunction="searchfunction"></fhc-searchbar>
<div id="nav-logo" class="d-none d-lg-block">
<div class="d-flex h-100 justify-content-between">
<a :href="rootUrl">
@@ -141,32 +124,22 @@ export default {
<theme-switch></theme-switch>
</div>
</div>
<div
id="header-usermenu-collapsible"
class="collapse multi-collapse collapse-horizontal show"
>
<div
:style="!isMobile ? '' : 'width: 51px'"
id="nav-user"
>
<button id="nav-user-btn" class="btn btn-link rounded-0" type="button" data-bs-toggle="collapse" data-bs-target="#nav-user-menu" aria-expanded="false" aria-controls="nav-user-menu">
<img :src="avatarUrl" :alt="$p.t('profilUpdate/profilBild')" class="bg-dark avatar rounded-circle border border-dark"/>
</button>
<ul ref="navUserDropdown"
@[\`shown.bs.collapse\`]="handleShowNavUser"
@[\`hide.bs.collapse\`]="handleHideNavUser"
id="nav-user-menu" class="top-100 end-0 collapse list-unstyled" aria-labelledby="nav-user-btn">
<li><a class="fhc-dark-bg btn rounded-0 d-block" :href="site_url + '/Cis/Profil'" id="menu-profil">Profil</a></li>
<li >
<cis-sprachen @languageChanged="fetchMenu"></cis-sprachen>
</li>
<li><hr class="dropdown-divider m-0 "></li>
<li ><a class="fhc-dark-bg btn rounded-0 d-block" :href="logoutUrl">Logout</a></li>
</ul>
</div>
<div id="nav-user">
<button id="nav-user-btn" class="btn btn-link rounded-0" type="button" data-bs-toggle="collapse" data-bs-target="#nav-user-menu" aria-expanded="false" aria-controls="nav-user-menu">
<img :src="avatarUrl" :alt="$p.t('profilUpdate/profilBild')" class="bg-dark avatar rounded-circle border border-dark"/>
</button>
<ul ref="navUserDropdown"
@[\`shown.bs.collapse\`]="handleShowNavUser"
@[\`hide.bs.collapse\`]="handleHideNavUser"
id="nav-user-menu" class="top-100 end-0 collapse list-unstyled" aria-labelledby="nav-user-btn">
<li><a class="fhc-dark-bg btn rounded-0 d-block" :href="site_url + '/Cis/Profil'" id="menu-profil">Profil</a></li>
<li >
<cis-sprachen @languageChanged="fetchMenu"></cis-sprachen>
</li>
<li><hr class="dropdown-divider m-0 "></li>
<li ><a class="fhc-dark-bg btn rounded-0 d-block" :href="logoutUrl">Logout</a></li>
</ul>
</div>
<nav id="nav-main" class="offcanvas offcanvas-start" tabindex="-1" aria-labelledby="nav-main-btn" data-bs-backdrop="false">
<div id="nav-main-sticky">
<div id="nav-main-toggle" class="position-static d-none d-lg-block ">
@@ -0,0 +1,334 @@
import {CoreFilterCmpt} from "../../../components/filter/Filter.js";
import ApiPaabgabe from '../../../api/factory/paabgabeUebersicht.js'
import Loader from "../../Loader.js";
export const ProjektabgabeUebersicht = {
name: "ProjektabgabeUebersicht",
props: {
viewData: Object // NOTE: this is inherited from router-view
},
components: {
CoreFilterCmpt,
Loader
},
data() {
return {
phrasenPromise: null,
phrasenResolved: false,
tabulatorUuid: Vue.ref(0),
tableBuiltResolve: null,
tableBuiltPromise: null,
studiengaenge: null,
abgabetypen: null,
termine: null,
abgaben: null,
defaultStudiengang: {
studiengang_kz: null,
kuerzel: '-'
},
defaultTyp: {
paabgabetyp_kurzbz: null,
bezeichnung: '-'
},
defaultTermin: {
termin: null,
termin_anzeige: Vue.computed(() => this.$p.t('ui/alle'))
},
selectedStudiengang: null,
selectedAbgabetyp: null,
selectedTermin: null,
personSearchString: null,
paabgabeTableOptions: {
height: Vue.ref(400),
index: 'paabgabe_id',
layout: 'fitColumns',
//~ placeholder: this.$p.t('global/noDataAvailable'),
columns: [
{
title: Vue.computed(() => this.$p.t('global/aktionen')), field: 'actions',
formatter: (cell, formatterParams, onRendered) => {
let container = document.createElement('div');
container.className = "d-flex gap-2";
let downloadButton = document.createElement('button');
downloadButton.className = 'btn btn-outline-secondary';
downloadButton.innerHTML = '<i class="fa fa-download"></i>';
downloadButton.title = this.$p.t('ui', 'downloadDok');
downloadButton.addEventListener('click', evt => {
evt.stopPropagation();
this.actionDownload(cell.getData().paabgabe_id);
});
container.append(downloadButton);
if (this.viewData.showEdit)
{
let editButton = document.createElement('button');
editButton.className = 'btn btn-outline-secondary';
editButton.innerHTML = '<i class="fa fa-edit"></i>';
//editButton.addEventListener('click', () =>
//this.$refs.edit.open(cell.getData())
//);
container.append(editButton);
}
return container;
}
},
{title: Vue.computed(() => this.$p.t('abgabetool/paabgabeid')), field: 'paabgabe_id', visible: false},
{title: Vue.computed(() => this.$p.t('abgabetool/projektarbeitid')), field: 'projektarbeit_id', visible: false},
{
title: Vue.computed(() => this.$p.t('abgabetool/termin')),
field: "termin",
widthGrow: 1,
formatter: function (cell) {
const dateStr = cell.getValue();
if (!dateStr) return "";
const date = new Date(dateStr);
return date.toLocaleString("de-DE", {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour12: false
});
}
},
{title: Vue.computed(() => this.$p.t('abgabetool/c4abgabetyp')), field: 'paabgabetyp_bezeichnung'},
{title: Vue.computed(() => this.$p.t('person/uid')), field: 'uid'},
{title: Vue.computed(() => this.$p.t('person/vorname')), field: 'vorname'},
{title: Vue.computed(() => this.$p.t('person/nachname')), field: 'nachname'},
{title: Vue.computed(() => this.$p.t('abgabetool/c4projekttyp')), field: 'projekttyp_kurzbz'},
{title: Vue.computed(() => this.$p.t('abgabetool/c4titel')), field: 'titel'},
{title: Vue.computed(() => this.$p.t('abgabetool/personStatus')), field: 'personStatus'},
{
title: "in Visual Library",
field: 'in_visual_library',
formatter: (cell) => {
return cell.getValue() ? this.$p.t('ui/ja') : this.$p.t('ui/nein');
}
}
],
persistence: false,
},
paabgabeTableEventHandlers: [{
event: "tableBuilt",
handler: async () => {
this.tableBuiltResolve()
}
}
]};
},
methods: {
tableResolve(resolve) {
this.tableBuiltResolve = resolve
},
setupData(){
//~ const d = data.map(paabgabe => {
//~ return {
//~ ort_kurzbz: paabgabe.ort_kurzbz,
//~ bezeichnung: paabgabe.bezeichnung.replace('&amp;', '&'),
//~ nummer: paabgabe.planbezeichnung,
//~ personen: paabgabe.max_person
//~ }
//~ })
this.$refs.paabgabeTable.tabulator.setData(this.abgaben);
},
// set placeholder text for no data table
setNoDataPlaceholder() {
this.$refs.paabgabeTable.tabulatorOptions.placeholder = this.$p.t('global/noDataAvailable');
},
loadStudiengaenge() {
this.$api.call(ApiPaabgabe.getStudiengaenge())
.then(res => {
this.studiengaenge = res?.data ?? []
})
},
loadPaabgabeTypes() {
this.$api.call(ApiPaabgabe.getPaAbgabetypen())
.then(res => {
this.abgabetypen = res?.data ?? []
})
},
loadTermine() {
this.$api.call(ApiPaabgabe.getTermine(this.selectedStudiengang, this.selectedAbgabetyp))
.then(res => {
this.selectedTermin = null;
this.termine = res?.data ?? []
})
},
loadPaAbgaben() {
this.$refs.loader.show();
this.$api.call(
ApiPaabgabe.getPaAbgaben(this.selectedStudiengang, this.selectedAbgabetyp, this.selectedTermin, this.personSearchString)
)
.then(res => {
this.$refs.loader.hide();
this.abgaben = res?.data ?? [];
this.setupData(res?.data ?? []);
});
},
handleUuidDefined(uuid) {
this.tabulatorUuid = uuid
},
//~ setRoute(val) {
//~ // TODO: router push
//~ },
async setupMounted() {
// load data for dropdowns
this.loadStudiengaenge();
this.loadPaabgabeTypes();
this.loadTermine();
// wait for table to build
this.tableBuiltPromise = new Promise(this.tableResolve);
await this.tableBuiltPromise;
// data placeholder after table built so phrases are available
this.setNoDataPlaceholder();
//this.loadPaAbgaben();
//~ const tableID = this.tabulatorUuid ? ('-' + this.tabulatorUuid) : ''
//~ const tableDataSet = document.getElementById('filterTableDataset' + tableID);
//~ if(!tableDataSet) return
//~ const rect = tableDataSet.getBoundingClientRect();
//~ const h = window.visualViewport.height - rect.top - 100
//~ if(this.$refs.raumsucheTable) {
//~ this.$refs.raumsucheTable.$refs.table.style.setProperty('height', h+'px')
//~ }
},
actionDownload(paabgabe_id) {
//~ window.open(
//~ FHC_JS_DATA_STORAGE_OBJECT.app_root
//~ + FHC_JS_DATA_STORAGE_OBJECT.ci_router
//~ +'/api/frontend/v1/education/paabgabeuebersicht/downloadProjektarbeit?paabgabe_id=' + encodeURIComponent(paabgabe_id),
//~ '_blank'
//~ );
},
// download zip file with all searched submission files
actionDownloadZip() {
const url = new URL(FHC_JS_DATA_STORAGE_OBJECT.app_root
+ FHC_JS_DATA_STORAGE_OBJECT.ci_router
+'/api/frontend/v1/education/PaabgabeUebersicht/downloadZip');
if (this.selectedStudiengang) url.searchParams.append('studiengang_kz', this.selectedStudiengang);
if (this.selectedAbgabetyp) url.searchParams.append('abgabetyp_kurzbz', this.selectedAbgabetyp);
if (this.selectedTermin) url.searchParams.append('abgabedatum', this.selectedTermin);
if (this.personSearchString) url.searchParams.append('personSearchString', this.personSearchString);
window.open(url.toString(), '_blank');
}
},
computed: {
isDarkMode() {
return this.$theme.theme_name.value == 'dark';
},
personSearchEnabled() {
return this.selectedStudiengang == null && this.selectedTermin == null && this.selectedAbgabetyp == null;
},
abgabeSearchEnabled() {
return this.personSearchString == '' || this.personSearchString == null;
},
zipDownloadUrl() {
return FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/api/frontend/v1/education/PaabgabeUebersicht/downloadZip';
}
},
created() {
this.phrasenPromise = this.$p.loadCategory(['abgabetool', 'global', 'person', 'ui']);
this.phrasenPromise.then(()=> {this.phrasenResolved = true});
},
mounted() {
this.setupMounted();
},
template: `
<h1 class="h3">{{$p.t('abgabetool/projektabgabeUebersicht')}}</h1>
<hr>
<div class="row">
<div class="col-12 col-lg-2">
<h6>{{ $p.t('abgabetool/studiengang') }}:</h6>
<select
ref="studiengang"
id="studiengangSelect"
v-model="selectedStudiengang"
class="form-select"
:aria-label="$p.t('abgabetool/studiengang_auswaehlen')"
:disabled="!abgabeSearchEnabled"
@change="loadTermine();"
>
<option :key="defaultStudiengang.studiengang_kz" selected :value="defaultStudiengang.studiengang_kz">{{defaultStudiengang.kuerzel}}</option>
<option v-for="stg in studiengaenge" :key="stg.studiengang_kz" :value="stg.studiengang_kz">{{stg.kuerzel}}</option>
</select>
</div>
<div class="col-12 col-lg-2">
<h6>{{ $p.t('abgabetool/c4abgabetyp') }}:</h6>
<select
ref="abgabetyp"
id="abgabetypSelect"
v-model="selectedAbgabetyp"
class="form-select"
:aria-label="$p.t('abgabetool/abgabetyp_auswaehlen')"
:disabled="!abgabeSearchEnabled"
@change="loadTermine();"
>
<option :key="defaultTyp.paabgabetyp_kurzbz" selected :value="defaultTyp.paabgabetyp_kurzbz">{{defaultTyp.bezeichnung}}</option>
<option v-for="typ in abgabetypen" :key="typ.paabgabetyp_kurzbz" :value="typ.paabgabetyp_kurzbz">{{typ.bezeichnung}}</option>
</select>
</div>
<div class="col-12 col-lg-2">
<h6>{{ $p.t('abgabetool/termin') }}:</h6>
<select
ref="termin"
id="terminSelect"
v-model="selectedTermin"
class="form-select"
:aria-label="$p.t('abgabetool/termin_auswaehlen')"
:disabled="!abgabeSearchEnabled"
>
<option :key="defaultTermin.termin" selected :value="defaultTermin.termin">{{defaultTermin.termin_anzeige}}</option>
<option v-for="termin in termine" :key="termin.termin" :value="termin.termin">{{termin.termin_anzeige}}</option>
</select>
</div>
<div class="col-12 col-lg-2">
<h6>{{ $p.t('abgabetool/personsuche') }}:</h6>
<input
type="text"
name="person-search"
class="form-control"
:placeholder="'name/uid/person ID/prestudent ID'"
:disabled="!personSearchEnabled"
v-on:keyup.enter="loadPaAbgaben"
v-model="personSearchString"
/>
</div>
<div class="col-12 col-lg-2 align-content-end">
<button class="btn btn-primary border-0" @click="loadPaAbgaben">{{ $p.t('abgabetool/anzeigen') }}</button>
</div>
<div class="col-12 col-lg-2 align-content-end">
<button class="btn btn-secondary border-0" @click="actionDownloadZip">{{ $p.t('abgabetool/zipDownload') }}</button>
</div>
</div>
<core-filter-cmpt
v-if="phrasenResolved"
@uuidDefined="handleUuidDefined"
ref="paabgabeTable"
:tabulator-options="paabgabeTableOptions"
:tabulator-events="paabgabeTableEventHandlers"
tableOnly
:sideMenu="false"
/>
<loader ref="loader" :timeout="0"></loader>
`
};
export default ProjektabgabeUebersicht;
+302 -350
View File
@@ -21,71 +21,69 @@ export default {
dms,
cms,
mergedStudent,
mergedPerson,
mergedPerson
},
props: {
searchoptions: {
type: Object,
required: true,
},
searchfunction: {
type: Function,
required: true,
},
showBtnSubmit: Boolean,
},
provide() {
return {
query: Vue.computed(() => this.lastQuery),
};
},
inject: ["isMobile"],
data: function () {
return {
searchtimer: null,
hidetimer: null,
searchsettings: {
searchstr: this.getSearchStr(),
types: this.getInitiallySelectedTypes(),
},
searchresult: [],
searchmode: "",
showresult: false,
searching: false,
error: null,
abortController: null,
props: {
searchoptions: {
type: Object,
required: true
},
searchfunction: {
type: Function,
required: true
},
showBtnSubmit: Boolean
},
provide() {
return {
query: Vue.computed(() => this.lastQuery)
};
},
data: function() {
return {
searchtimer: null,
hidetimer: null,
searchsettings: {
searchstr: this.getSearchStr(),
types: this.getInitiallySelectedTypes(),
},
searchresult: [],
searchmode: '',
showresult: false,
searching: false,
error: null,
abortController: null,
settingsDropdown: null,
lastQuery: "",
isSearchShownInMobileView: false,
};
},
lastQuery: ''
};
},
computed: {
searchTypesPlaceholder() {
if (!this.searchsettings.types.length) {
return Object.values(this.typeLabels).join(" / ");
return Object.values(this.typeLabels).join(' / ');
}
return this.searchsettings.types
.map((type) => this.typeLabels[type])
.join(" / ");
return this.searchsettings.types.map(type => this.typeLabels[type]).join(' / ');
},
types() {
if (!this.searchoptions.types) return [];
if (!this.searchoptions.types)
return [];
if (Array.isArray(this.searchoptions.types))
return this.searchoptions.types;
return Object.keys(this.searchoptions.types);
},
typeLabels() {
if (!this.searchoptions.types) return {};
if (!this.searchoptions.types)
return {};
if (Array.isArray(this.searchoptions.types)) {
return this.searchoptions.types.reduce((res, type) => {
res[type] = type;
return res;
return res
}, {});
}
return this.searchoptions.types;
},
}
},
template: /*html*/ `
template: /*html*/`
<form
ref="searchform"
class="d-flex me-3"
@@ -94,91 +92,80 @@ export default {
@focusin="searchfocusin"
@focusout="searchfocusout"
>
<span
v-if="isMobile"
type="button"
data-bs-toggle="collapse"
data-bs-target=".multi-collapse"
aria-controls="header-searchbar-collapsible header-options-collapsible header-usermenu-collapsible"
aria-expanded="false"
class="d-flex flex-row align-items-center ps-3 pe-1"
>
<i v-if="isSearchShownInMobileView" class="fa-solid fa-chevron-left"></i>
<i v-else class="fa-solid fa-magnifying-glass"></i>
</span>
<div
:class="{'flex-grow-1': !isMobile, 'collapse multi-collapse collapse-horizontal': isMobile}"
id="header-searchbar-collapsible"
@[\`show.bs.collapse\`]="isSearchShownInMobileView = true"
@[\`hidden.bs.collapse\`]="isSearchShownInMobileView = false"
ref="searchbox"
class="h-100 input-group me-2 searchbar_searchbox"
:class="showresult ? 'open' : 'closed'"
>
<div
:class="{open: showresult, closed: showresult, 'px-3': isMobile}"
ref="searchbox"
class="h-100 input-group me-2 searchbar_searchbox"
:style="isMobile ? 'width: ' + getMaxWidthOfSearchbarInMobileView() : ''"
<span class="input-group-text">
<i class="fa-solid fa-magnifying-glass"></i>
</span>
<input
ref="input"
@keyup="search"
@focus="showsearchresult"
v-model="searchsettings.searchstr"
class="form-control searchbar_input"
type="search"
:placeholder="$p.t('search/input_search_label', { types: searchTypesPlaceholder })"
:aria-label="$p.t('search/input_search_label', { types: searchTypesPlaceholder })"
>
<button
v-if="searchsettings.searchstr"
type="button"
class="searchbar_input_clear btn btn-outline-secondary"
@click="clearInput"
@focusin.stop
>
<span class="input-group-text">
<i class="fa-solid fa-magnifying-glass color-white"></i>
</span>
<input
ref="input"
@keyup="search"
@focus="showsearchresult"
v-model="searchsettings.searchstr"
class="form-control searchbar_input"
type="search"
:placeholder="$p.t('search/input_search_label', { types: searchTypesPlaceholder })"
:aria-label="$p.t('search/input_search_label', { types: searchTypesPlaceholder })"
>
<button
v-if="showBtnSubmit"
type="submit"
class="btn btn-primary"
:title="$p.t('search/submit')"
:aria-label="$p.t('search/submit')"
>
<i class="fas fa-search"></i>
</button>
<button
data-bs-toggle="collapse"
data-bs-target="#searchSettings"
aria-expanded="false"
aria-controls="searchSettings"
ref="settingsbutton"
class="searchbar_setting_btn btn btn-secondary"
type="button"
:title="$p.t('search/button_filter_label')"
:aria-label="$p.t('search/button_filter_label')"
>
<i class="fas fa-filter"></i>
</button>
</div>
<div v-show="showresult"
class="searchbar_results" tabindex="-1">
<div class="searchbar_results_scroller" ref="result">
<div class="searchbar_results_wrapper" ref="results">
<div v-if="searching">
<i class="fas fa-spinner fa-spin fa-2x"></i>
</div>
<div v-else-if="this.error !== null">{{ error }}</div>
<div v-else-if="searchresult.length < 1">{{ $p.t('search/error_no_results') }}</div>
<template v-else v-for="res in searchresult">
<component
v-if="isValidRenderer(res.renderer)"
:is="res.renderer"
:mode="searchmode"
:res="res"
:actions="getActions(res)"
@actionexecuted="hideresult"
></component>
<div v-else class="searchbar-result text-danger fw-bold">{{ $p.t('search/error_unknown_type', res) }}</div>
</template>
</div>
</div>
</div>
</div>
<i class="fas fa-close"></i>
</button>
<button
v-if="showBtnSubmit"
type="submit"
class="btn btn-primary"
:title="$p.t('search/submit')"
:aria-label="$p.t('search/submit')"
>
<i class="fas fa-search"></i>
</button>
<button
data-bs-toggle="collapse"
data-bs-target="#searchSettings"
aria-expanded="false"
aria-controls="searchSettings"
ref="settingsbutton"
class="searchbar_setting_btn btn btn-secondary"
type="button"
:title="$p.t('search/button_filter_label')"
:aria-label="$p.t('search/button_filter_label')"
>
<i class="fas fa-cog"></i>
</button>
</div>
<div v-show="showresult"
class="searchbar_results" tabindex="-1">
<div class="searchbar_results_scroller" ref="result">
<div class="searchbar_results_wrapper" ref="results">
<div v-if="searching">
<i class="fas fa-spinner fa-spin fa-2x"></i>
</div>
<div v-else-if="this.error !== null">{{ error }}</div>
<div v-else-if="searchresult.length < 1">{{ $p.t('search/error_no_results') }}</div>
<template v-else v-for="res in searchresult">
<component
v-if="isValidRenderer(res.renderer)"
:is="res.renderer"
:mode="searchmode"
:res="res"
:actions="getActions(res)"
@actionexecuted="hideresult"
></component>
<div v-else class="searchbar-result text-danger fw-bold">{{ $p.t('search/error_unknown_type', res) }}</div>
</template>
</div>
</div>
</div>
<div
id="searchSettings"
@@ -220,42 +207,34 @@ export default {
</div>
</form>
`,
watch: {
"searchsettings.searchstr": function (newSearchValue) {
if (this.searchoptions.origin) {
sessionStorage.setItem(
`${this.searchoptions.origin}_searchstr`,
newSearchValue,
);
watch:{
'searchsettings.searchstr': function (newSearchValue) {
if(this.searchoptions.origin){
sessionStorage.setItem(`${this.searchoptions.origin}_searchstr`,newSearchValue);
}
},
"searchsettings.types"(newValue) {
'searchsettings.types'(newValue) {
if (Array.isArray(newValue) && newValue.length === 0) {
this.searchsettings.types = [...this.types];
}
// stores the search types in the localstorage, only if the newValue is also an array
if (Array.isArray(newValue) && this.searchoptions.origin) {
localStorage.setItem(
`${this.searchoptions.origin}_searchtypes`,
JSON.stringify(newValue),
);
localStorage.setItem(`${this.searchoptions.origin}_searchtypes`, JSON.stringify(newValue));
}
this.search();
},
},
mounted() {
}
},
mounted(){
this.settingsDropdown = new bootstrap.Collapse(this.$refs.settings, {
toggle: false,
toggle: false
});
if (!this.searchoptions.origin) {
console.warn(
"No origin defined in the searchoptions for the searchbar, please define the origin property in the searchbaroptions to allow reliable storage of searchstr and searchtypes accross applications.",
);
if (!this.searchoptions.origin){
console.warn("No origin defined in the searchoptions for the searchbar, please define the origin property in the searchbaroptions to allow reliable storage of searchstr and searchtypes accross applications.");
}
},
updated() {
if (this.showresult) {
if(this.showresult) {
Vue.nextTick(() => {
this.calcSearchResultHeight();
});
@@ -270,34 +249,32 @@ export default {
getInitiallySelectedTypes() {
let result = false;
if (this.searchoptions.origin) {
let localStorageValue = localStorage.getItem(
`${this.searchoptions.origin}_searchtypes`,
);
let localStorageValue = localStorage.getItem(`${this.searchoptions.origin}_searchtypes`);
if (localStorageValue) {
result = JSON.parse(localStorageValue);
}
}
if (result) return result;
if (!this.searchoptions.types) return [];
if (result)
return result;
if (!this.searchoptions.types)
return [];
if (Array.isArray(this.searchoptions.types))
return [...this.searchoptions.types];
return Object.keys(this.searchoptions.types);
},
getSearchStr: function () {
if (!this.searchoptions.origin) return "";
return (
sessionStorage.getItem(
`${this.searchoptions.origin}_searchstr`,
) ?? ""
);
getSearchStr: function(){
if (!this.searchoptions.origin)
return '';
return sessionStorage.getItem(`${this.searchoptions.origin}_searchstr`) ?? '';
},
checkSettingsVisibility: function (event) {
checkSettingsVisibility: function(event) {
// hides the settings collapsible if the user clicks somewhere else
if (!this.$refs.settings.contains(event.target)) {
if (!this.$refs.settings.contains(event.target))
{
this.settingsDropdown.hide();
}
},
handleShowSettings: function () {
handleShowSettings: function() {
// adds the event listener checkSettingsVisibility only when the collapsible is shown
document.addEventListener("click", this.checkSettingsVisibility);
},
@@ -305,208 +282,183 @@ export default {
// removes the event listener checkSettingsVisibility when the collapsible is hidden
document.removeEventListener("click", this.checkSettingsVisibility);
},
calcSearchResultHeight: function () {
calcSearchResultHeight: function() {
const rect = this.$refs.results.getBoundingClientRect();
if (rect.height > 0 && rect.height < window.innerHeight * 0.8) {
this.$refs.result.style.height =
Math.ceil(rect.height + 16) + "px";
if( rect.height > 0 && rect.height < (window.innerHeight * 0.8) ) {
this.$refs.result.style.height = Math.ceil(rect.height + 16) + 'px';
} else {
this.$refs.result.style.height =
Math.floor(window.innerHeight * 0.8) + "px";
this.$refs.result.style.height = Math.floor(window.innerHeight * 0.8) + 'px';
}
},
calcSearchResultExtent: function () {
if (!this.showresult) {
calcSearchResultExtent: function() {
if(!this.showresult) {
return;
}
if (
this.searchoptions?.calcheightonly === undefined ||
this.searchoptions.calcheightonly === false
) {
if(this.searchoptions?.calcheightonly === undefined
|| this.searchoptions.calcheightonly === false) {
var rect = this.$refs.searchbox.getBoundingClientRect();
this.$refs.result.style.top =
Math.floor(rect.bottom + 3) + "px";
this.$refs.result.style.right = Math.floor(rect.right) + "px";
this.$refs.result.style.width = Math.floor(rect.width) + "px";
this.$refs.result.style.top = Math.floor(rect.bottom + 3) + 'px';
this.$refs.result.style.right = Math.floor(rect.right) + 'px';
this.$refs.result.style.width = Math.floor(rect.width) + 'px';
}
this.calcSearchResultHeight();
},
search: function () {
if (this.searchoptions?.nolivesearch === true) return;
this.calcSearchResultHeight();
},
search: function() {
if(this.searchoptions?.nolivesearch === true) return;
this.abort();
if (this.searchsettings.searchstr.length >= 2) {
this.abort();
if( this.searchsettings.searchstr.length >= 2 ) {
this.calcSearchResultExtent();
this.searchtimer = setTimeout(
this.callsearchapi,
500
);
} else {
this.showresult = false;
}
},
abort() {
if (this.searchtimer !== null) {
clearTimeout(this.searchtimer);
}
if (this.abortController) {
this.abortController.abort();
this.abortController = null;
}
this.searchresult = [];
},
callsearchapi: function() {
this.error = null;
this.searchresult.splice(0, this.searchresult.length);
this.searching = true;
this.showsearchresult();
if(this.searchsettings.types.length === 0) {
this.error = this.$p.t('search/error_missing_type');
this.searching = false;
return;
}
if (this.abortController)
this.abortController.abort();
this.abortController = new AbortController();
this.searchfunction(this.searchsettings, { timeout: 50000, signal: this.abortController.signal })
.then(response=>{
if (!response.data) {
this.error = this.$p.t('search/error_general');
} else {
let res = response.data.map(el => el.data ? {...el, ...JSON.parse(el.data)} : el);
this.lastQuery = response.meta.searchstring;
if (this.searchoptions.mergeResults) {
let counter = 0;
let mergeTypes = [];
let mergedType = 'merged-';
let mergeKey = '';
switch (this.searchoptions.mergeResults) {
case 'student':
mergeTypes = ['student', 'prestudent'];
mergedType += this.searchoptions.mergeResults;
mergeKey = 'uid';
break;
case 'person':
mergeTypes = ['person', 'employee', 'student', 'prestudent'];
mergedType += this.searchoptions.mergeResults;
mergeKey = 'person_id';
break;
}
if (mergeTypes.length) {
res = Object.values(res.reduce((a, c) => {
if (!mergeTypes.includes(c.renderer)) {
a['nomerge' + counter++] = c;
} else if (c[mergeKey] === null) {
a['nomerge' + counter++] = c;
} else if (a[c[mergeKey]] === undefined) {
a[c[mergeKey]] = {
rank: c.rank,
renderer: mergedType,
type: mergedType,
list: [c]
};
} else {
a[c[mergeKey]].list.push(c);
if (c.rank > a[c[mergeKey]].rank)
a[c[mergeKey]].rank = c.rank;
}
return a;
}, {})).sort((a, b) => b.rank - a.rank);
}
}
this.searchresult = res;
this.searchmode = response.meta.mode;
}
this.searching = false;
this.retry = 0;
})
.catch(error=> {
if (error.code == "ERR_CANCELED") {
return this.retry = 0;
}
if (error.code == "ECONNABORTED" && this.retry) {
this.retry--;
return this.callsearchapi();
}
this.error = this.$p.t('search/error_general', error);
this.searching = false;
this.retry = 0;
});
},
refreshsearch: function() {
this.search();
this.togglesettings();
},
hideresult: function() {
this.showresult = false;
window.removeEventListener('resize', this.calcSearchResultExtent);
},
showsearchresult: function() {
if(this.searchoptions?.nolivesearch === true) return;
if( this.searchsettings.searchstr.length >= 2 ) {
this.showresult = true;
window.addEventListener('resize', this.calcSearchResultExtent);
this.calcSearchResultExtent();
this.searchtimer = setTimeout(this.callsearchapi, 500);
} else {
this.showresult = false;
}
},
abort() {
if (this.searchtimer !== null) {
clearTimeout(this.searchtimer);
}
if (this.abortController) {
this.abortController.abort();
this.abortController = null;
}
this.searchresult = [];
},
callsearchapi: function () {
this.error = null;
this.searchresult.splice(0, this.searchresult.length);
this.searching = true;
this.showsearchresult();
if (this.searchsettings.types.length === 0) {
this.error = this.$p.t("search/error_missing_type");
this.searching = false;
return;
}
if (this.abortController) this.abortController.abort();
this.abortController = new AbortController();
this.searchfunction(this.searchsettings, {
timeout: 50000,
signal: this.abortController.signal,
})
.then((response) => {
if (!response.data) {
this.error = this.$p.t("search/error_general");
} else {
let res = response.data.map((el) =>
el.data ? { ...el, ...JSON.parse(el.data) } : el,
);
this.lastQuery = response.meta.searchstring;
if (this.searchoptions.mergeResults) {
let counter = 0;
let mergeTypes = [];
let mergedType = "merged-";
let mergeKey = "";
switch (this.searchoptions.mergeResults) {
case "student":
mergeTypes = ["student", "prestudent"];
mergedType +=
this.searchoptions.mergeResults;
mergeKey = "uid";
break;
case "person":
mergeTypes = [
"person",
"employee",
"student",
"prestudent",
];
mergedType +=
this.searchoptions.mergeResults;
mergeKey = "person_id";
break;
}
if (mergeTypes.length) {
res = Object.values(
res.reduce((a, c) => {
if (!mergeTypes.includes(c.renderer)) {
a["nomerge" + counter++] = c;
} else if (c[mergeKey] === null) {
a["nomerge" + counter++] = c;
} else if (
a[c[mergeKey]] === undefined
) {
a[c[mergeKey]] = {
rank: c.rank,
renderer: mergedType,
type: mergedType,
list: [c],
};
} else {
a[c[mergeKey]].list.push(c);
if (c.rank > a[c[mergeKey]].rank)
a[c[mergeKey]].rank = c.rank;
}
return a;
}, {}),
).sort((a, b) => b.rank - a.rank);
}
}
this.searchresult = res;
this.searchmode = response.meta.mode;
}
this.searching = false;
this.retry = 0;
})
.catch((error) => {
if (error.code == "ERR_CANCELED") {
return (this.retry = 0);
}
if (error.code == "ECONNABORTED" && this.retry) {
this.retry--;
return this.callsearchapi();
}
this.error = this.$p.t("search/error_general", error);
this.searching = false;
this.retry = 0;
});
},
refreshsearch: function () {
this.search();
this.togglesettings();
},
hideresult: function () {
this.showresult = false;
window.removeEventListener("resize", this.calcSearchResultExtent);
},
showsearchresult: function () {
if (this.searchoptions?.nolivesearch === true) return;
if (this.searchsettings.searchstr.length >= 2) {
this.showresult = true;
window.addEventListener("resize", this.calcSearchResultExtent);
this.calcSearchResultExtent();
}
},
searchfocusin: function (e) {
e.preventDefault();
e.stopPropagation();
if (this.hidetimer !== null) {
clearTimeout(this.hidetimer);
}
if (
this.searchsettings.searchstr.length >= 2 &&
this.searchresult.length === 0
) {
}
},
searchfocusin: function(e) {
e.preventDefault();
e.stopPropagation();
if( this.hidetimer !== null ) {
clearTimeout(this.hidetimer);
}
if (this.searchsettings.searchstr.length >= 2
&& this.searchresult.length === 0) {
this.search();
}
},
searchfocusout: function (e) {
e.preventDefault();
e.stopPropagation();
this.hidetimer = setTimeout(this.hideresult, 100);
},
dash2camelCase(string) {
return string.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
},
isValidRenderer(renderer) {
const camelCaseRenderer = this.dash2camelCase(renderer);
return Object.keys(this.$.components).includes(camelCaseRenderer);
},
},
searchfocusout: function(e) {
e.preventDefault();
e.stopPropagation();
this.hidetimer = setTimeout(
this.hideresult,
100
);
},
dash2camelCase(string) {
return string.replace(/-([a-z])/g, g => g[1].toUpperCase());
},
isValidRenderer(renderer) {
const camelCaseRenderer = this.dash2camelCase(renderer);
return Object.keys(this.$.components).includes(camelCaseRenderer);
},
getActions(res) {
let actions =
this.searchoptions.actions[this.dash2camelCase(res.renderer)];
let actions = this.searchoptions.actions[this.dash2camelCase(res.renderer)];
if (actions) {
return actions;
}
return this.searchoptions.actions[res.type];
},
getMaxWidthOfSearchbarInMobileView() {
// body width - hardcoded chevron width; necessary for accurate collapse transition transition
return (
document.querySelector("body").getBoundingClientRect().width -
27 +
"px"
);
},
},
}
}
};
+282
View File
@@ -58177,6 +58177,288 @@ I have been informed that I am under no obligation to consent to the transmissio
)
),
// ### Phrases Dashboard Admin END
//**************************** CIS Projektabgabeuebersicht start
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'projektabgabeUebersicht',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Projektabgabe Übersicht',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Project work submission overview',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'studiengang',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Studiengang',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'study program',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'studiengangWaehlen',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Studiengang auswählen',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Select study program',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'abgabetypWaehlen',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Abgabetyp auswählen',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Select type of submission',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'terminWaehlen',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Termin auswählen',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Select due date',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'personsuche',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Personsuche',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'person search',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'anzeigen',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Abgaben anzeigen',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Show submissions',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'zipDownload',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'ZIP herunterladen',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Download ZIP',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'paabgabeid',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Projektabgabe ID',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'thesis submission ID',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'projektarbeitid',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Projektarbeit ID',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'projekt work ID',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'termin',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Termin',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'due date',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'personStatus',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Personstatus',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'person status',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'nichtsAusgewaehlt',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Nichts ausgewählt',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'No selection',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'keineDateienVorhanden',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Keine Dateien vorhanden',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'No files available',
'description' => '',
'insertvon' => 'system'
)
)
),
//**************************** CIS Projektabgabeuebersicht ende
);