mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-10-12 15:04:04 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d768f89f52 | ||
|
|
b6f9d53a29 | ||
|
|
bf17450131 | ||
|
|
0c4d102850 | ||
|
|
2e15f2c5ab | ||
|
|
784e5693b4 | ||
|
|
88b49b70e9 | ||
|
|
67a33f2ceb | ||
|
|
751445c199 | ||
|
|
916f5dd68e | ||
|
|
efc8fa1eeb | ||
|
|
d58edff92d | ||
|
|
643929a0c6 | ||
|
|
4ba2869632 | ||
|
|
293bb77179 | ||
|
|
3fe10d715d | ||
|
|
3034615455 | ||
|
|
36064afbba | ||
|
|
e369a8c03f | ||
|
|
faad2c4a9a | ||
|
|
2ffaf36270 | ||
|
|
c8c7d1566c | ||
|
|
9d4218bf38 | ||
|
|
67f30c7765 |
@@ -129,13 +129,19 @@ $route['api/frontend/v1/treemenudata/stv/.*'] = 'api/frontend/v1/stv/students/in
|
||||
$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/stg/(:any)'] = 'api/frontend/v1/Lehrveranstaltung/getByStg/$1/$2';
|
||||
$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/stg/(:any)/semester/(:any)'] = 'api/frontend/v1/Lehrveranstaltung/getByStg/$1/$2/$3';
|
||||
$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/stg/(:any)/semester/(:any)/.*'] = 'api/frontend/v1/Lehrveranstaltung/getByStg/$1/$2/$3';
|
||||
$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/stg/(:any)/orgform/(:any)'] = 'api/frontend/v1/Lehrveranstaltung/getByStg/$1/$2/$3';
|
||||
$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/stg/(:any)/orgform/(:any)/.*'] = 'api/frontend/v1/Lehrveranstaltung/getByStg/$1/$2/$3';
|
||||
|
||||
$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/stg/(:any)/orgform/(:any)/semester/(:any)'] = 'api/frontend/v1/Lehrveranstaltung/getByStgOrgform/$1/$2/$3/$4';
|
||||
$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/stg/(:any)/orgform/(:any)/semester/(:any)/.*'] = 'api/frontend/v1/Lehrveranstaltung/getByStgOrgform/$1/$2/$3/$4';
|
||||
$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/stg/(:any)/orgform/(:any)'] = 'api/frontend/v1/Lehrveranstaltung/getByStgOrgform/$1/$2/$3';
|
||||
$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/stg/(:any)/orgform/(:any)/.*'] = 'api/frontend/v1/Lehrveranstaltung/getByStgOrgform/$1/$2/$3';
|
||||
|
||||
$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/emp/(:any)'] = 'api/frontend/v1/Lehrveranstaltung/getByEmp/$1/$2';
|
||||
$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/emp/(:any)/stg/(:any)'] = 'api/frontend/v1/Lehrveranstaltung/getByEmp/$1/$2/$3';
|
||||
$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/emp/(:any)/stg/(:any)/.*'] = 'api/frontend/v1/Lehrveranstaltung/getByEmp/$1/$2/$3';
|
||||
|
||||
$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/le/(:any)'] = 'api/frontend/v1/Lehrveranstaltung/getByLe/$1/$2';
|
||||
$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/lv/(:any)'] = 'api/frontend/v1/Lehrveranstaltung/getByLv/$1/$2';
|
||||
|
||||
|
||||
// load routes from extensions, also look for environment-specific configs
|
||||
$subdirs = ['application/config/extensions', 'application/config/' . ENVIRONMENT . '/extensions'];
|
||||
|
||||
@@ -26,7 +26,8 @@ $config['root'] = [
|
||||
'verband' => [
|
||||
'group'
|
||||
]
|
||||
],
|
||||
'orgform'
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$config['root']['stg']['orgform'] = $config['root']['stg'];
|
||||
|
||||
@@ -1916,7 +1916,7 @@ class Abgabe extends FHCAPI_Controller
|
||||
|
||||
if (!$mailres)
|
||||
{
|
||||
$this->terminateWithError($this->p->t('abgabetool', 'c4fehlerMailBegutachterv2'), 'general');
|
||||
$this->terminateWithError($this->p->t('abgabetool', 'c4fehlerMailZweitBegutachterv2'), 'general');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,7 +28,10 @@ class Lehrveranstaltung extends FHCAPI_Controller
|
||||
{
|
||||
parent::__construct([
|
||||
'getByEmp' => ['admin:r', 'assistenz:r'],
|
||||
'getByLe' => ['admin:r', 'assistenz:r'],
|
||||
'getByLv' => ['admin:r', 'assistenz:r'],
|
||||
'getByStg' => ['admin:r', 'assistenz:r'],
|
||||
'getByStgOrgform' => ['admin:r', 'assistenz:r'],
|
||||
'loadByLV' => ['admin:r', 'assistenz:r'],
|
||||
]);
|
||||
|
||||
@@ -77,41 +80,117 @@ class Lehrveranstaltung extends FHCAPI_Controller
|
||||
|
||||
$this->terminateWithSuccess($tree);
|
||||
}
|
||||
|
||||
public function getByLe($studiensemester_kurzbz = null, $lehreinheit_id = null)
|
||||
{
|
||||
if (is_null($lehreinheit_id))
|
||||
$this->terminateWithError($this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL);
|
||||
|
||||
$studiensemester_kurzbz = $this->getStudiensemesterKurzbz($studiensemester_kurzbz);
|
||||
|
||||
|
||||
$lehreinheiten = $this->_ci->LehreinheitModel->getByLeStudiensemester($lehreinheit_id, $studiensemester_kurzbz);
|
||||
$lehreinheiten_data = $this->getDataOrTerminateWithError($lehreinheiten);
|
||||
|
||||
$this->terminateWithSuccess($lehreinheiten_data);
|
||||
}
|
||||
|
||||
public function getByLv($studiensemester_kurzbz = null, $lehrveranstaltung_id = null)
|
||||
{
|
||||
if (is_null($lehrveranstaltung_id))
|
||||
$this->terminateWithError($this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL);
|
||||
|
||||
$studiensemester_kurzbz = $this->getStudiensemesterKurzbz($studiensemester_kurzbz);
|
||||
|
||||
$tree = [];
|
||||
|
||||
$this->_ci->LehrveranstaltungModel->addSelect('studiengang_kz');
|
||||
$lv = $this->_ci->LehrveranstaltungModel->load($lehrveranstaltung_id);
|
||||
$lv = $this->getDataOrTerminateWithError($lv);
|
||||
|
||||
if (empty($lv))
|
||||
$this->terminateWithError($this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL);
|
||||
|
||||
$lehrveranstaltungen_data = array();
|
||||
foreach ($this->_loadLvRows(current($lv)->studiengang_kz, $studiensemester_kurzbz) as $row)
|
||||
{
|
||||
if ((string)$row->lehrveranstaltung_id === (string)$lehrveranstaltung_id)
|
||||
{
|
||||
$lehrveranstaltungen_data[] = $row;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($lehrveranstaltungen_data))
|
||||
{
|
||||
|
||||
$this->_ci->LehrveranstaltungModel->addSelect('
|
||||
tbl_lehrveranstaltung.*,
|
||||
tbl_lehrveranstaltung.kurzbz as lv_kurzbz,
|
||||
lehrveranstaltung_id,
|
||||
lehrtyp_kurzbz,
|
||||
studiengang_kz as lv_studiengang_kz,
|
||||
UPPER(CONCAT(tbl_studiengang.typ, tbl_studiengang.kurzbz)) as studiengang,
|
||||
semester as semester,
|
||||
tbl_lehrveranstaltung.bezeichnung as lv_bezeichnung,
|
||||
ects as lv_ects,
|
||||
lehreverzeichnis as lv_lehreverzeichnis,
|
||||
planfaktor as lv_planfaktor,
|
||||
planlektoren as lv_planlektoren,
|
||||
planpersonalkosten as lv_planpersonalkosten,
|
||||
plankostenprolektor as lv_plankostenprolektor,
|
||||
tbl_lehrveranstaltung.orgform_kurzbz as orgform_kurzbz,
|
||||
tbl_lehrveranstaltung.sprache as sprache,
|
||||
lehrform_kurzbz as lv_lehrform_kurzbz,
|
||||
tbl_lehrveranstaltung.bezeichnung_english as lv_bezeichnung_english,
|
||||
semesterstunden as lv_semesterstunden,
|
||||
');
|
||||
$this->_ci->LehrveranstaltungModel->addJoin('public.tbl_studiengang', 'studiengang_kz');
|
||||
$lehrveranstaltungen = $this->_ci->LehrveranstaltungModel->load($lehrveranstaltung_id);
|
||||
$lehrveranstaltungen_data = $this->getDataOrTerminateWithError($lehrveranstaltungen);
|
||||
}
|
||||
|
||||
foreach ($lehrveranstaltungen_data as $lehrveranstaltung)
|
||||
{
|
||||
$lehreinheiten = $this->_ci->LehreinheitModel->getByLvidStudiensemester($lehrveranstaltung->lehrveranstaltung_id, $studiensemester_kurzbz);
|
||||
$lehreinheiten_data = $this->getDataOrTerminateWithError($lehreinheiten);
|
||||
|
||||
if (!isset($lehrveranstaltung->_children))
|
||||
{
|
||||
$lehrveranstaltung->_children = $lehreinheiten_data;
|
||||
}
|
||||
$tree[] = $lehrveranstaltung;
|
||||
}
|
||||
|
||||
$counter = 0;
|
||||
$this->assignUniqueIndex($tree, $counter);
|
||||
$this->terminateWithSuccess($tree);
|
||||
}
|
||||
|
||||
public function getByStg($studiensemester_kurzbz = null, $stg_kz = null, $semester = null)
|
||||
{
|
||||
$this->_getLvTree($studiensemester_kurzbz, $stg_kz, $semester, null);
|
||||
}
|
||||
|
||||
public function getByStgOrgform($studiensemester_kurzbz = null, $stg_kz = null, $orgform = null, $semester = null)
|
||||
{
|
||||
$orgform_kurzbz = $this->getOrgformKurzbz($orgform);
|
||||
$this->_getLvTree($studiensemester_kurzbz, $stg_kz, $semester, $orgform_kurzbz);
|
||||
}
|
||||
|
||||
private function _getLvTree($studiensemester_kurzbz, $stg_kz, $semester, $orgform_kurzbz)
|
||||
{
|
||||
$studiengang_kz = $this->getStudiengangKz($stg_kz);
|
||||
|
||||
if (is_null($studiengang_kz) || !preg_match("/^(-?[1-9][0-9]*|0)$/", (string)$studiengang_kz))
|
||||
$this->terminateWithError($this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL);
|
||||
|
||||
$verband = null;
|
||||
if (!is_null($semester) && !is_numeric($semester))
|
||||
{
|
||||
$verband = $this->getOrgformKurzbz($semester);
|
||||
$semester = null;
|
||||
}
|
||||
|
||||
$this->_ci->load->model('organisation/Studienplan_model', 'StudienplanModel');
|
||||
if (!is_null($semester) && !ctype_digit((string)$semester))
|
||||
$this->terminateWithError($this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL);
|
||||
|
||||
$studiensemester_kurzbz = $this->getStudiensemesterKurzbz($studiensemester_kurzbz);
|
||||
$studienplan_data = $this->_ci->StudienplanModel->getStudienplaeneBySemester($studiengang_kz, $studiensemester_kurzbz, $semester, $verband);
|
||||
|
||||
$studienplan_ids = array();
|
||||
$only_ids = array();
|
||||
$placeholders = array();
|
||||
|
||||
if (hasData($studienplan_data))
|
||||
{
|
||||
foreach (getData($studienplan_data) as $studienplan) {
|
||||
$placeholders[] = "(?, ?)";
|
||||
$studienplan_ids[] = $studienplan->studienplan_id;
|
||||
$studienplan_ids[] = $studienplan->semester;
|
||||
$only_ids[] = $studienplan->studienplan_id;
|
||||
}
|
||||
}
|
||||
|
||||
$lehrveranstaltungen_data = $this->_ci->LehrveranstaltungModel->getLvsByStudiengang($studienplan_ids, $placeholders, $only_ids, $studiengang_kz, $studiensemester_kurzbz, $semester, $verband);
|
||||
$lehrveranstaltungen_data = hasData($lehrveranstaltungen_data) ? getData($lehrveranstaltungen_data) : array();
|
||||
$lehrveranstaltungen_data = $this->_loadLvRows($studiengang_kz, $studiensemester_kurzbz, $semester, $orgform_kurzbz);
|
||||
|
||||
$tree = [];
|
||||
foreach ($lehrveranstaltungen_data as $row)
|
||||
@@ -244,6 +323,31 @@ class Lehrveranstaltung extends FHCAPI_Controller
|
||||
|
||||
$this->LehreinheitModel->getLvsByFachbereich($fachbereich, $studiensemester_kurzbz, $mitarbeiter_uid);
|
||||
}*/
|
||||
|
||||
private function _loadLvRows($studiengang_kz, $studiensemester_kurzbz, $semester = null, $orgform_kurzbz = null)
|
||||
{
|
||||
$this->_ci->load->model('organisation/Studienplan_model', 'StudienplanModel');
|
||||
|
||||
$studienplan_data = $this->_ci->StudienplanModel->getStudienplaeneBySemester($studiengang_kz, $studiensemester_kurzbz, $semester, $orgform_kurzbz);
|
||||
|
||||
$studienplan_ids = array();
|
||||
$only_ids = array();
|
||||
$placeholders = array();
|
||||
|
||||
if (hasData($studienplan_data))
|
||||
{
|
||||
foreach (getData($studienplan_data) as $studienplan) {
|
||||
$placeholders[] = "(?, ?)";
|
||||
$studienplan_ids[] = $studienplan->studienplan_id;
|
||||
$studienplan_ids[] = $studienplan->semester;
|
||||
$only_ids[] = $studienplan->studienplan_id;
|
||||
}
|
||||
}
|
||||
|
||||
$lehrveranstaltungen_data = $this->_ci->LehrveranstaltungModel->getLvsByStudiengang($studienplan_ids, $placeholders, $only_ids, $studiengang_kz, $studiensemester_kurzbz, $semester, $orgform_kurzbz);
|
||||
return hasData($lehrveranstaltungen_data) ? getData($lehrveranstaltungen_data) : array();
|
||||
}
|
||||
|
||||
private function _setAuthUID()
|
||||
{
|
||||
$this->_uid = getAuthUID();
|
||||
@@ -309,8 +413,8 @@ class Lehrveranstaltung extends FHCAPI_Controller
|
||||
$data = $this->getDataOrTerminateWithError($result);
|
||||
|
||||
if (!$data)
|
||||
return $orgform_kurzbz;
|
||||
$this->terminateWithError($this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL);
|
||||
|
||||
return current($data)->orgform_kurzbz;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,19 +25,7 @@ if (! defined('BASEPATH')) exit('No direct script access allowed');
|
||||
*/
|
||||
class Ort extends FHCAPI_Controller
|
||||
{
|
||||
// advanced filters of getRooms: tbl_ort column => filter type, the same list as in Raumsuche.js.
|
||||
// text: contains, case-insensitive; select: equals; range: <column>_min, <column>_max; flag: true; exists: not null
|
||||
const ADVANCED_FILTERS = [
|
||||
'ort_kurzbz' => 'text',
|
||||
'bezeichnung' => 'text',
|
||||
'planbezeichnung' => 'text',
|
||||
'stockwerk' => 'range',
|
||||
'gebteil' => 'select',
|
||||
'm2' => 'range',
|
||||
'lehre' => 'flag',
|
||||
'content_id' => 'exists'
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Object initialization
|
||||
*/
|
||||
@@ -48,9 +36,7 @@ class Ort extends FHCAPI_Controller
|
||||
'ContentID' => self::PERM_LOGGED,
|
||||
'getOrtKurzbzContent' => self::PERM_LOGGED,
|
||||
'getRooms' => self::PERM_LOGGED,
|
||||
'getTypes' => self::PERM_LOGGED,
|
||||
'getStandorte' => self::PERM_LOGGED,
|
||||
'getAdvancedFilterOptions' => self::PERM_LOGGED
|
||||
'getTypes' => self::PERM_LOGGED
|
||||
]);
|
||||
|
||||
$this->load->model('ressource/Ort_model', 'OrtModel');
|
||||
@@ -70,17 +56,6 @@ class Ort extends FHCAPI_Controller
|
||||
$this->form_validation->set_rules('datum','Datum','required');
|
||||
$this->form_validation->set_rules('von','Uhrzeit Von','required|regex_match[/^[0-9]{2}:[0-9]{2}$/]');
|
||||
$this->form_validation->set_rules('bis','Uhrzeit Bis','required|regex_match[/^[0-9]{2}:[0-9]{2}$/]');
|
||||
$this->form_validation->set_rules('standort_id','Standort','regex_match[/^([0-9]+|none)$/]');
|
||||
foreach (self::ADVANCED_FILTERS as $column => $type) {
|
||||
if ($type == 'range') {
|
||||
$this->form_validation->set_rules($column.'_min', $column, 'numeric');
|
||||
$this->form_validation->set_rules($column.'_max', $column, 'numeric');
|
||||
} elseif ($type == 'flag' || $type == 'exists') {
|
||||
$this->form_validation->set_rules($column, $column, 'in_list[true]');
|
||||
} else {
|
||||
$this->form_validation->set_rules($column, $column, 'max_length[255]');
|
||||
}
|
||||
}
|
||||
if($this->form_validation->run() == FALSE) {
|
||||
$this->terminateWithValidationErrors($this->form_validation->error_array());
|
||||
}
|
||||
@@ -90,7 +65,6 @@ class Ort extends FHCAPI_Controller
|
||||
$bis = $this->input->get('bis', TRUE);
|
||||
$typ = $this->input->get('typ', TRUE);
|
||||
$personenanzahl = $this->input->get('personenanzahl', TRUE);
|
||||
$standort_id = $this->input->get('standort_id', TRUE);
|
||||
|
||||
|
||||
$this->load->model('ressource/Mitarbeiter_model', 'MitarbeiterModel');
|
||||
@@ -101,33 +75,21 @@ class Ort extends FHCAPI_Controller
|
||||
$bisStunde = getData($this->StundeModel->getStundeForTime($bis))[0]->stunde;
|
||||
|
||||
$params = array();
|
||||
$qry = "SELECT DISTINCT tbl_ort.*, tbl_standort.bezeichnung AS standort
|
||||
$qry = "SELECT DISTINCT tbl_ort.*
|
||||
FROM public.tbl_ort JOIN public.tbl_ortraumtyp USING(ort_kurzbz)
|
||||
LEFT JOIN public.tbl_standort USING(standort_id)
|
||||
WHERE aktiv AND reservieren";
|
||||
WHERE aktiv AND lehre AND ort_kurzbz NOT LIKE '\\\\_%'";
|
||||
if($typ) {
|
||||
$params[] = $typ;
|
||||
$qry.= " AND raumtyp_kurzbz = ?";
|
||||
$qry.= "AND raumtyp_kurzbz = ?";
|
||||
}
|
||||
|
||||
if($standort_id === 'none') { // rooms without a Standort
|
||||
$qry.= " AND standort_id IS NULL";
|
||||
} elseif($standort_id) {
|
||||
$params[] = $standort_id;
|
||||
$qry.= " AND standort_id = ?";
|
||||
} else {
|
||||
$qry.= " AND standort_id IS NOT NULL";
|
||||
}
|
||||
|
||||
$qry.= $this->advancedFilterSql($params);
|
||||
|
||||
if(!$isMitarbeiter) { // students are only allowed to get a subset defined by config
|
||||
$qry.= ' AND raumtyp_kurzbz IN ?';
|
||||
$params[] = $this->config->item('roomtypes_student');
|
||||
$this->addMeta('config', $this->config->item('roomtypes_student'));
|
||||
}
|
||||
|
||||
$qry.= " AND (max_person>= ? OR max_person is null)";
|
||||
$qry.= "AND (max_person>= ? OR max_person is null)";
|
||||
$params[] = $personenanzahl;
|
||||
|
||||
$qry.=" AND ort_kurzbz NOT IN
|
||||
@@ -165,41 +127,6 @@ class Ort extends FHCAPI_Controller
|
||||
$this->terminateWithSuccess(getData($result));
|
||||
}
|
||||
|
||||
/**
|
||||
* Standorte of the rooms that pass the base filter of getRooms
|
||||
*/
|
||||
public function getStandorte()
|
||||
{
|
||||
$qry = "SELECT DISTINCT standort_id, tbl_standort.bezeichnung
|
||||
FROM public.tbl_ort JOIN public.tbl_standort USING(standort_id)
|
||||
WHERE aktiv AND reservieren
|
||||
ORDER BY tbl_standort.bezeichnung";
|
||||
|
||||
$result = $this->OrtModel->execReadOnlyQuery($qry);
|
||||
|
||||
$this->terminateWithSuccess($this->getDataOrTerminateWithError($result));
|
||||
}
|
||||
|
||||
/**
|
||||
* Values of the select filters: column => distinct values of the rooms that pass the base filter of getRooms
|
||||
*/
|
||||
public function getAdvancedFilterOptions()
|
||||
{
|
||||
$options = [];
|
||||
foreach (self::ADVANCED_FILTERS as $column => $type) {
|
||||
if ($type != 'select')
|
||||
continue;
|
||||
|
||||
$result = $this->OrtModel->execReadOnlyQuery("SELECT DISTINCT ".$column." AS value
|
||||
FROM public.tbl_ort
|
||||
WHERE aktiv AND reservieren AND ".$column." <> ''
|
||||
ORDER BY ".$column);
|
||||
$options[$column] = array_column($this->getDataOrTerminateWithError($result), 'value');
|
||||
}
|
||||
|
||||
$this->terminateWithSuccess($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a JSON body via HTTP POST and provides the parameters
|
||||
*/
|
||||
@@ -247,49 +174,5 @@ class Ort extends FHCAPI_Controller
|
||||
|
||||
$this->terminateWithSuccess($content);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------
|
||||
// Private methods
|
||||
|
||||
/**
|
||||
* SQL conditions of the advanced filters in the GET parameters, adds their values to $params
|
||||
*/
|
||||
private function advancedFilterSql(&$params)
|
||||
{
|
||||
// no xss_clean: the values are bound, and it could change a select value so that it no longer equals the column
|
||||
$sql = '';
|
||||
foreach (self::ADVANCED_FILTERS as $column => $type) {
|
||||
if ($type == 'range') {
|
||||
$min = $this->input->get($column.'_min');
|
||||
$max = $this->input->get($column.'_max');
|
||||
if ($min !== null && $min !== '') {
|
||||
$sql.= " AND tbl_ort.".$column." >= ?::numeric";
|
||||
$params[] = $min;
|
||||
}
|
||||
if ($max !== null && $max !== '') {
|
||||
$sql.= " AND tbl_ort.".$column." <= ?::numeric";
|
||||
$params[] = $max;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $this->input->get($column);
|
||||
if ($value === null || $value === '')
|
||||
continue;
|
||||
|
||||
if ($type == 'flag') {
|
||||
$sql.= " AND tbl_ort.".$column;
|
||||
} elseif ($type == 'exists') {
|
||||
$sql.= " AND tbl_ort.".$column." IS NOT NULL";
|
||||
} elseif ($type == 'select') {
|
||||
$sql.= " AND tbl_ort.".$column." = ?";
|
||||
$params[] = $value;
|
||||
} else {
|
||||
$sql.= " AND strpos(lower(tbl_ort.".$column."), lower(?)) > 0";
|
||||
$params[] = $value;
|
||||
}
|
||||
}
|
||||
return $sql;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,8 @@ class Studiensemester extends FHCAPI_Controller
|
||||
'getStudienjahrByStudiensemester' => self::PERM_LOGGED,
|
||||
'getAllStudiensemesterAndAktOrNext' => self::PERM_LOGGED,
|
||||
'current' => self::PERM_LOGGED,
|
||||
'set' => self::PERM_LOGGED
|
||||
'set' => self::PERM_LOGGED,
|
||||
'getContainingOrNearestByDateRange' => self::PERM_LOGGED,
|
||||
)
|
||||
);
|
||||
// Load model StudiensemesterModel
|
||||
@@ -203,4 +204,15 @@ class Studiensemester extends FHCAPI_Controller
|
||||
|
||||
$this->terminateWithSuccess(true);
|
||||
}
|
||||
|
||||
public function getContainingOrNearestByDateRange()
|
||||
{
|
||||
$from = $this->input->get("from");
|
||||
$to = $this->input->get("to");
|
||||
|
||||
$result = $this->StudiensemesterModel->getContainingOrNearestByDateRange($from, $to);
|
||||
$data = $this->getDataOrTerminateWithError($result);
|
||||
|
||||
$this->terminateWithSuccess($data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -574,6 +574,19 @@ class Kontakt extends FHCAPI_Controller
|
||||
$person_id = $this->input->post('person_id');
|
||||
$standort_id = $this->input->post('standort_id');
|
||||
|
||||
//check person_id: allow editing only of persons who are no mitarbeiter
|
||||
if($person_id)
|
||||
{
|
||||
$this->load->model('person/Benutzer_model', 'BenutzerModel');
|
||||
$this->BenutzerModel->addJoin('public.tbl_mitarbeiter ma', 'ON (ma.mitarbeiter_uid = public.tbl_benutzer.uid)');
|
||||
|
||||
$result = $this->BenutzerModel->loadWhere([
|
||||
'person_id' => $person_id
|
||||
]);
|
||||
if(hasData($result))
|
||||
return $this->terminateWithError($this->p->t('ui', 'editingMitarbeiterPVonly'), self::ERROR_TYPE_GENERAL);
|
||||
}
|
||||
|
||||
$result = $this->KontaktModel->update(
|
||||
[
|
||||
'kontakt_id' => $kontakt_id
|
||||
|
||||
@@ -33,7 +33,7 @@ class SearchBarLib
|
||||
const ERROR_NOT_AUTH = 'ERR005';
|
||||
|
||||
// List of allowed types of search
|
||||
const ALLOWED_TYPES = ['mitarbeiter', 'mitarbeiter_ohne_zuordnung', 'organisationunit', 'raum', 'person', 'student','studentStv', 'prestudent', 'document', 'cms'];
|
||||
const ALLOWED_TYPES = ['mitarbeiter', 'mitarbeiter_ohne_zuordnung', 'organisationunit', 'raum', 'person', 'student','studentStv', 'prestudent', 'document', 'cms', 'lehreinheit', 'lehrveranstaltung'];
|
||||
|
||||
const PHOTO_IMG_URL = '/cis/public/bild.php?src=person&person_id=';
|
||||
|
||||
@@ -180,10 +180,14 @@ class SearchBarLib
|
||||
protected function buildSearchClause(DB_Model $dbModel, array $columns, $searchstr)
|
||||
{
|
||||
$searchstr = preg_replace('/[[:punct:]]/', ' ', $searchstr);
|
||||
$words = preg_split('/\s+/', trim($searchstr), -1, PREG_SPLIT_NO_EMPTY);
|
||||
|
||||
if (empty($words)) return array();
|
||||
|
||||
$document = implode(' || \' \' || ', $columns);
|
||||
$query = '\'' . implode(':* & ', explode(' ', trim($searchstr))) . ':*\'';
|
||||
$reversequery = '\'*:' . implode(' & *:', explode(' ', trim($searchstr))) . '\'';
|
||||
$nospacequery = '\'' . implode('', explode(' ', trim($searchstr))) . ':*\'';
|
||||
$query = '\'' . implode(':* & ', $words) . ':*\'';
|
||||
$reversequery = '\'*:' . implode(' & *:', $words) . '\'';
|
||||
$nospacequery = '\'' . implode('', $words) . ':*\'';
|
||||
|
||||
$searchclause = <<<EOSC
|
||||
to_tsvector(lower(regexp_replace({$document}, '[[:punct:]]', ' ', 'g'))) @@ to_tsquery(lower({$query}))
|
||||
@@ -596,5 +600,98 @@ EOSC;
|
||||
// Otherwise return an empty array
|
||||
return array();
|
||||
}
|
||||
|
||||
|
||||
private function _lehreinheit($searchstr, $type)
|
||||
{
|
||||
$dbModel = new DB_Model();
|
||||
|
||||
$idwhere = '';
|
||||
if (ctype_digit(trim($searchstr)))
|
||||
{
|
||||
$id = (int)trim($searchstr);
|
||||
$idwhere = ' OR le.lehreinheit_id = ' . $id;
|
||||
}
|
||||
|
||||
$lehreinheit = $dbModel->execReadOnlyQuery('
|
||||
SELECT
|
||||
\'teachingunit\' AS renderer,
|
||||
\''.$type.'\' AS type,
|
||||
le.lehreinheit_id AS id,
|
||||
le.studiensemester_kurzbz,
|
||||
lv.bezeichnung,
|
||||
\'lv_table_icon icon-\' AS foto,
|
||||
UPPER(stg.typ || stg.kurzbz) AS studiengang,
|
||||
le.lehrform_kurzbz,
|
||||
lv.semester
|
||||
FROM lehre.tbl_lehreinheit le
|
||||
JOIN lehre.tbl_lehrveranstaltung lv ON (lv.lehrveranstaltung_id = le.lehrveranstaltung_id)
|
||||
JOIN public.tbl_studiengang stg ON (stg.studiengang_kz = lv.studiengang_kz)
|
||||
JOIN public.tbl_studiensemester sem ON (sem.studiensemester_kurzbz = le.studiensemester_kurzbz)
|
||||
WHERE (' .
|
||||
$this->buildSearchClause(
|
||||
$dbModel,
|
||||
array(
|
||||
'(stg.typ || stg.kurzbz)',
|
||||
'lv.semester',
|
||||
'lv.bezeichnung'
|
||||
),
|
||||
$searchstr
|
||||
) . ')' . $idwhere . '
|
||||
');
|
||||
|
||||
// If something has been found then return it
|
||||
if (hasData($lehreinheit)) return getData($lehreinheit);
|
||||
|
||||
// Otherwise return an empty array
|
||||
return array();
|
||||
}
|
||||
|
||||
private function _lehrveranstaltung($searchstr, $type)
|
||||
{
|
||||
$dbModel = new DB_Model();
|
||||
|
||||
$idwhere = '';
|
||||
|
||||
if (ctype_digit(trim($searchstr)))
|
||||
{
|
||||
$id = (int)trim($searchstr);
|
||||
$idwhere = ' OR tbl_lehrveranstaltung.lehrveranstaltung_id = ' . $id;
|
||||
}
|
||||
|
||||
$lehreinheit = $dbModel->execReadOnlyQuery('
|
||||
|
||||
SELECT
|
||||
\'teachingunit\' AS renderer,
|
||||
\''.$type.'\' AS type,
|
||||
lehrveranstaltung_id as id,
|
||||
tbl_lehrveranstaltung.bezeichnung,
|
||||
\'lv_table_icon icon-lv\' as foto,
|
||||
UPPER(tbl_studiengang.typ::varchar(1) || tbl_studiengang.kurzbz) as studiengang,
|
||||
semester,
|
||||
lehrform_kurzbz,
|
||||
tbl_organisationseinheit.bezeichnung as oe_bezeichnung
|
||||
FROM lehre.tbl_lehrveranstaltung
|
||||
JOIN public.tbl_studiengang USING(studiengang_kz)
|
||||
JOIN public.tbl_organisationseinheit ON tbl_lehrveranstaltung.oe_kurzbz = tbl_organisationseinheit.oe_kurzbz
|
||||
WHERE (' .
|
||||
$this->buildSearchClause(
|
||||
$dbModel,
|
||||
array(
|
||||
'(tbl_studiengang.typ || tbl_studiengang.kurzbz)',
|
||||
'semester',
|
||||
'tbl_lehrveranstaltung.bezeichnung'
|
||||
),
|
||||
$searchstr
|
||||
) . ')' . $idwhere . '
|
||||
');
|
||||
|
||||
// If something has been found then return it
|
||||
if (hasData($lehreinheit)) return getData($lehreinheit);
|
||||
|
||||
// Otherwise return an empty array
|
||||
return array();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -159,8 +159,6 @@ class LvVwLib
|
||||
if (!$stg->mischform)
|
||||
return [];
|
||||
|
||||
$this->_ci->StudiengangModel->db->select('TRUE AS leaf', false);
|
||||
|
||||
return $this->_ci->stglib->orgform($path_template, $stg->studiengang_kz);
|
||||
}
|
||||
|
||||
|
||||
@@ -340,6 +340,20 @@ EOSQL;
|
||||
}
|
||||
|
||||
|
||||
public function getByLeStudiensemester($lehreinheit_id, $studiensemester_kurzbz)
|
||||
{
|
||||
$qry = "WITH lehreinheiten AS (
|
||||
SELECT *
|
||||
FROM lehre.tbl_lehreinheit
|
||||
WHERE lehreinheit_id = ?
|
||||
AND studiensemester_kurzbz = ?
|
||||
)," . $this->_getLeCTE();
|
||||
|
||||
$params = array($lehreinheit_id, $studiensemester_kurzbz);
|
||||
|
||||
return $this->execReadOnlyQuery($qry, $params);
|
||||
|
||||
}
|
||||
public function getByLvidStudiensemester($lv_id, $studiensemester_kurzbz, $mitarbeiter_uid = null, $fachbereich_kurzbz = null)
|
||||
{
|
||||
$qry = "WITH lehreinheiten AS (
|
||||
@@ -347,35 +361,7 @@ EOSQL;
|
||||
FROM lehre.tbl_lehreinheit
|
||||
WHERE lehrveranstaltung_id = ?
|
||||
AND studiensemester_kurzbz = ?
|
||||
),
|
||||
". $this->_getGruppenCTE() . ",
|
||||
". $this->_getLektorenCTE() . ",
|
||||
". $this->_getFachbereichCTE() . ",
|
||||
". $this->_getTagsCTE() . "
|
||||
|
||||
SELECT lehreinheiten.*,
|
||||
lehreinheiten.lehrform_kurzbz as lv_lehrform_kurzbz,
|
||||
tbl_lehrveranstaltung.kurzbz as lv_kurzbz,
|
||||
tbl_lehrveranstaltung.bezeichnung as lv_bezeichnung,
|
||||
COALESCE(tag_data_agg.tags, '[]'::json) AS tags,
|
||||
gruppen.gruppen,
|
||||
mitarbeiter.lektoren,
|
||||
mitarbeiter.le_planstunden,
|
||||
mitarbeiter.vorname,
|
||||
mitarbeiter.nachname,
|
||||
mitarbeiter.semesterstunden,
|
||||
fachbereich.bezeichnung as fachbereich,
|
||||
UPPER(CONCAT(tbl_studiengang.typ,tbl_studiengang.kurzbz)) as studiengang,
|
||||
semester
|
||||
FROM lehreinheiten
|
||||
LEFT JOIN lehre.tbl_lehrveranstaltung ON tbl_lehrveranstaltung.lehrveranstaltung_id = lehreinheiten.lehrfach_id
|
||||
LEFT JOIN public.tbl_studiengang USING(studiengang_kz)
|
||||
LEFT JOIN tag_data_agg ON tag_data_agg.lehreinheit_id = lehreinheiten.lehreinheit_id
|
||||
LEFT JOIN mitarbeiter ON lehreinheiten.lehreinheit_id = mitarbeiter.lehreinheit_id
|
||||
LEFT JOIN fachbereich ON lehreinheiten.lehreinheit_id = fachbereich.lehreinheit_id
|
||||
LEFT JOIN gruppen ON lehreinheiten.lehreinheit_id = gruppen.lehreinheit_id
|
||||
WHERE true
|
||||
";
|
||||
)," . $this->_getLeCTE();
|
||||
|
||||
$params = array($lv_id, $studiensemester_kurzbz);
|
||||
|
||||
@@ -395,9 +381,83 @@ EOSQL;
|
||||
return $this->execReadOnlyQuery($qry, $params);
|
||||
}
|
||||
|
||||
private function getLVTmp($stg_kz = null)
|
||||
public function getOffeneStunden($lehreinheit_ids)
|
||||
{
|
||||
$qry = "SELECT DISTINCT ON(lehrveranstaltung_id) *,
|
||||
if (!is_array($lehreinheit_ids))
|
||||
$lehreinheit_ids = [$lehreinheit_ids];
|
||||
|
||||
if (empty($lehreinheit_ids))
|
||||
return success([]);
|
||||
|
||||
$db = new DB_Model();
|
||||
$placeholders = implode(',', array_fill(0, count($lehreinheit_ids), '?'));
|
||||
|
||||
$qry = "WITH eventdauer AS (
|
||||
SELECT
|
||||
tbl_kalender_lehreinheit.lehreinheit_id,
|
||||
tbl_kalender.von,
|
||||
tbl_kalender.bis,
|
||||
tbl_kalender.kalender_id
|
||||
FROM lehre.tbl_lehreinheit
|
||||
JOIN lehre.tbl_kalender_lehreinheit USING(lehreinheit_id)
|
||||
JOIN lehre.tbl_kalender USING(kalender_id)
|
||||
WHERE tbl_lehreinheit.lehreinheit_id IN ($placeholders)
|
||||
AND NOT EXISTS (SELECT 1 FROM lehre.tbl_kalender nachfolger
|
||||
WHERE nachfolger.vorgaenger_kalender_id = tbl_kalender.kalender_id)
|
||||
AND tbl_kalender.status_kurzbz NOT IN ('deleted', 'todelete', 'archived')
|
||||
GROUP BY tbl_kalender_lehreinheit.lehreinheit_id, tbl_kalender.von, tbl_kalender.bis, tbl_kalender.kalender_id
|
||||
),
|
||||
raster AS (
|
||||
SELECT
|
||||
eventdauer.lehreinheit_id,
|
||||
eventdauer.kalender_id,
|
||||
eventdauer.von,
|
||||
eventdauer.bis,
|
||||
vonstunde.stunde AS vonstunde_nr,
|
||||
bisstunde.stunde AS bisstunde_nr
|
||||
FROM eventdauer
|
||||
LEFT JOIN lehre.tbl_stunde vonstunde ON eventdauer.von::time = vonstunde.beginn
|
||||
LEFT JOIN lehre.tbl_stunde bisstunde ON eventdauer.bis::time = bisstunde.ende
|
||||
),
|
||||
unterrichtsdauer AS (
|
||||
SELECT
|
||||
raster.lehreinheit_id,
|
||||
raster.kalender_id,
|
||||
sum(EXTRACT(EPOCH FROM (LEAST(stunde.ende, raster.bis::time) - GREATEST(stunde.beginn, raster.von::time))) / 60) AS minuten
|
||||
FROM raster JOIN lehre.tbl_stunde stunde ON stunde.beginn < raster.bis::time AND stunde.ende > raster.von::time
|
||||
WHERE raster.vonstunde_nr IS NULL OR raster.bisstunde_nr IS NULL
|
||||
GROUP BY raster.lehreinheit_id, raster.kalender_id
|
||||
),
|
||||
verplantestunden AS (
|
||||
SELECT raster.lehreinheit_id, raster.kalender_id,
|
||||
CASE
|
||||
WHEN raster.vonstunde_nr IS NOT NULL AND raster.bisstunde_nr IS NOT NULL
|
||||
THEN (raster.bisstunde_nr - raster.vonstunde_nr + 1)
|
||||
ELSE coalesce(unterrichtsdauer.minuten, 0) / 45
|
||||
END AS verplantestunde
|
||||
FROM raster LEFT JOIN unterrichtsdauer ON unterrichtsdauer.lehreinheit_id = raster.lehreinheit_id AND unterrichtsdauer.kalender_id = raster.kalender_id
|
||||
),
|
||||
verplant_gesamt AS (
|
||||
SELECT lehreinheit_id, SUM(verplantestunde) AS verplant
|
||||
FROM verplantestunden
|
||||
GROUP BY lehreinheit_id
|
||||
)
|
||||
SELECT
|
||||
lema.lehreinheit_id,
|
||||
lema.mitarbeiter_uid,
|
||||
lema.planstunden,
|
||||
ROUND(coalesce(verplant_gesamt.verplant, 0), 2) AS verplant,
|
||||
ROUND((lema.planstunden - coalesce(verplant_gesamt.verplant, 0)), 2) AS offenestunden
|
||||
FROM lehre.tbl_lehreinheitmitarbeiter lema
|
||||
LEFT JOIN verplant_gesamt ON verplant_gesamt.lehreinheit_id = lema.lehreinheit_id
|
||||
WHERE lema.lehreinheit_id IN ($placeholders)";
|
||||
|
||||
return $db->execReadOnlyQuery($qry, array_merge($lehreinheit_ids, $lehreinheit_ids));
|
||||
}
|
||||
|
||||
private function getLVTmp()
|
||||
{
|
||||
return "SELECT DISTINCT ON(lehrveranstaltung_id) *,
|
||||
'' as stundenblockung,
|
||||
'' as lehreinheit_id,
|
||||
'' as wochenrythmus,
|
||||
@@ -408,24 +468,33 @@ EOSQL;
|
||||
'' as studienplan_beeichnung,
|
||||
UPPER(CONCAT(vw_lehreinheit.stg_typ, vw_lehreinheit.stg_kurzbz)) as studiengang
|
||||
FROM campus.vw_lehreinheit
|
||||
WHERE mitarbeiter_uid = ?
|
||||
AND studiensemester_kurzbz = ?";
|
||||
|
||||
if (!is_null($stg_kz)) {
|
||||
$qry .= " AND lv_studiengang_kz = ?";
|
||||
}
|
||||
|
||||
return $qry;
|
||||
WHERE studiensemester_kurzbz = ? ";
|
||||
}
|
||||
|
||||
public function getLvsByEmployee($mitarbeiter_uid, $studiensemester_kurzbz, $stg_kz = null)
|
||||
public function getLvsById($lehrveranstaltung_id, $studiensemester_kurzbz)
|
||||
{
|
||||
$qry = "WITH lvs AS (" . $this->getLVTmp($stg_kz) . ")
|
||||
$where = ' AND lehrveranstaltung_id = ?';
|
||||
$qry = "WITH lvs AS (" . $this->getLVTmp() . $where .")
|
||||
SELECT lvs.*
|
||||
FROM lvs
|
||||
";
|
||||
";
|
||||
|
||||
$params = array($studiensemester_kurzbz, $lehrveranstaltung_id);
|
||||
return $this->execReadOnlyQuery($qry, $params);
|
||||
}
|
||||
public function getLvsByEmployee($mitarbeiter_uid, $studiensemester_kurzbz, $stg_kz = null)
|
||||
{
|
||||
$where = ' AND mitarbeiter_uid = ?';
|
||||
|
||||
if ($stg_kz != null)
|
||||
$where .= " AND lv_studiengang_kz = ?";
|
||||
|
||||
$qry = "WITH lvs AS (" . $this->getLVTmp() . $where .")
|
||||
SELECT lvs.*
|
||||
FROM lvs";
|
||||
|
||||
$params = array($studiensemester_kurzbz, $mitarbeiter_uid);
|
||||
|
||||
$params = array($mitarbeiter_uid, $studiensemester_kurzbz);
|
||||
if (!is_null($stg_kz))
|
||||
{
|
||||
$params[] = $stg_kz;
|
||||
@@ -632,6 +701,36 @@ EOSQL;
|
||||
return success('Contract successfully updated.');
|
||||
}
|
||||
|
||||
private function _getLeCTE()
|
||||
{
|
||||
return $this->_getGruppenCTE() . ",
|
||||
". $this->_getLektorenCTE() . ",
|
||||
". $this->_getFachbereichCTE() . ",
|
||||
". $this->_getTagsCTE() . "
|
||||
|
||||
SELECT lehreinheiten.*,
|
||||
lehreinheiten.lehrform_kurzbz as lv_lehrform_kurzbz,
|
||||
tbl_lehrveranstaltung.kurzbz as lv_kurzbz,
|
||||
tbl_lehrveranstaltung.bezeichnung as lv_bezeichnung,
|
||||
COALESCE(tag_data_agg.tags, '[]'::json) AS tags,
|
||||
gruppen.gruppen,
|
||||
mitarbeiter.lektoren,
|
||||
mitarbeiter.le_planstunden,
|
||||
mitarbeiter.vorname,
|
||||
mitarbeiter.nachname,
|
||||
mitarbeiter.semesterstunden,
|
||||
fachbereich.bezeichnung as fachbereich,
|
||||
UPPER(CONCAT(tbl_studiengang.typ,tbl_studiengang.kurzbz)) as studiengang,
|
||||
semester
|
||||
FROM lehreinheiten
|
||||
LEFT JOIN lehre.tbl_lehrveranstaltung ON tbl_lehrveranstaltung.lehrveranstaltung_id = lehreinheiten.lehrfach_id
|
||||
LEFT JOIN public.tbl_studiengang USING(studiengang_kz)
|
||||
LEFT JOIN tag_data_agg ON tag_data_agg.lehreinheit_id = lehreinheiten.lehreinheit_id
|
||||
LEFT JOIN mitarbeiter ON lehreinheiten.lehreinheit_id = mitarbeiter.lehreinheit_id
|
||||
LEFT JOIN fachbereich ON lehreinheiten.lehreinheit_id = fachbereich.lehreinheit_id
|
||||
LEFT JOIN gruppen ON lehreinheiten.lehreinheit_id = gruppen.lehreinheit_id
|
||||
WHERE true";
|
||||
}
|
||||
private function _getGruppenCTE()
|
||||
{
|
||||
return "gruppen AS (
|
||||
|
||||
@@ -292,13 +292,29 @@ class Studiensemester_model extends DB_Model
|
||||
COALESCE(
|
||||
(SELECT studiensemester_kurzbz FROM semester_interval_startdate),
|
||||
(SELECT studiensemester_kurzbz FROM nearest_semester_interval_startdate)
|
||||
) AS studiensemester_kurzbz
|
||||
) AS studiensemester_kurzbz,
|
||||
COALESCE(
|
||||
(SELECT start FROM semester_interval_startdate),
|
||||
(SELECT start FROM nearest_semester_interval_startdate)
|
||||
) as start,
|
||||
COALESCE(
|
||||
(SELECT ende FROM semester_interval_startdate),
|
||||
(SELECT ende FROM nearest_semester_interval_startdate)
|
||||
) as ende
|
||||
UNION
|
||||
SELECT
|
||||
COALESCE(
|
||||
(SELECT studiensemester_kurzbz FROM semester_interval_enddate),
|
||||
(SELECT studiensemester_kurzbz FROM nearest_semester_interval_enddate)
|
||||
) AS studiensemester_kurzbz
|
||||
) AS studiensemester_kurzbz,
|
||||
COALESCE(
|
||||
(SELECT start FROM semester_interval_enddate),
|
||||
(SELECT start FROM nearest_semester_interval_enddate)
|
||||
) as start,
|
||||
COALESCE(
|
||||
(SELECT ende FROM semester_interval_enddate),
|
||||
(SELECT ende FROM nearest_semester_interval_enddate)
|
||||
) as ende
|
||||
|
||||
EOSQL;
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ $includesArray = array(
|
||||
'public/css/components/FilterComponent.css',
|
||||
'public/css/components/Profil.css',
|
||||
'public/css/components/FormUnderline.css',
|
||||
'public/css/components/inViewTooltip.css',
|
||||
'public/css/components/abgabetool/abgabe.css',
|
||||
'public/css/Cis4/Cms.css',
|
||||
'public/css/Cis4/Studium.css'
|
||||
|
||||
@@ -21,6 +21,7 @@ $this->load->view('templates/FHC-Header', $includesArray);
|
||||
?>
|
||||
|
||||
<script type="text/javascript">
|
||||
document.body.classList.add("cis4");
|
||||
if (window.self !== window.top)
|
||||
document.body.classList.add("in-frame");
|
||||
</script>
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
* Manuela Thamer <[email protected]>
|
||||
*/
|
||||
require_once(dirname(__FILE__).'/basis_db.class.php');
|
||||
require_once(dirname(__FILE__).'/mitarbeiter.class.php');
|
||||
|
||||
class entwicklungsteam extends basis_db
|
||||
{
|
||||
@@ -43,7 +44,6 @@ class entwicklungsteam extends basis_db
|
||||
public $ext_id;
|
||||
|
||||
public $besqual;
|
||||
public $studiengang_kz_old;
|
||||
|
||||
/**
|
||||
* Konstruktor
|
||||
@@ -74,7 +74,7 @@ class entwicklungsteam extends basis_db
|
||||
$qry = "SELECT * FROM bis.tbl_entwicklungsteam JOIN bis.tbl_besqual USING(besqualcode)
|
||||
WHERE entwicklungsteam_id=".$this->db_add_param($entwicklungsteam_id);
|
||||
|
||||
$qry.=";";
|
||||
$qry.=";";
|
||||
|
||||
if($this->db_query($qry))
|
||||
{
|
||||
@@ -147,6 +147,11 @@ class entwicklungsteam extends basis_db
|
||||
$this->errormsg = 'Es muss ein Mitarbeiter angegeben werden';
|
||||
return false;
|
||||
}
|
||||
if(!$this->checkIfExistingUid($this->mitarbeiter_uid))
|
||||
{
|
||||
$this->errormsg = 'Bitte einen Eintrag aus dem Dropdown Personensuche auswählen!';
|
||||
return false;
|
||||
}
|
||||
if($this->studiengang_kz=='')
|
||||
{
|
||||
$this->errormsg = 'Es muss ein Studiengang angegeben werden';
|
||||
@@ -162,6 +167,26 @@ class entwicklungsteam extends basis_db
|
||||
$this->errormsg = 'Endedatum darf nicht vor Anfangsdatum liegen';
|
||||
return false;
|
||||
}
|
||||
|
||||
if($this->beginn != '')
|
||||
{
|
||||
$beginnParts = explode('-', $this->beginn);
|
||||
if (!checkdate((int)$beginnParts[1], (int)$beginnParts[2], (int)$beginnParts[0])) {
|
||||
$this->errormsg = 'Anfangsdatum ist ungültig';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if($this->ende != '')
|
||||
{
|
||||
$endeParts = explode('-', $this->ende);
|
||||
if (!checkdate((int)$endeParts[1], (int)$endeParts[2], (int)$endeParts[0]))
|
||||
{
|
||||
$this->errormsg = 'Endedatum ist ungültig';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -183,30 +208,28 @@ class entwicklungsteam extends basis_db
|
||||
//Neuen Datensatz anlegen
|
||||
$qry = "INSERT INTO bis.tbl_entwicklungsteam (mitarbeiter_uid, studiengang_kz, besqualcode, beginn, ende,
|
||||
updateamum, updatevon, insertamum, insertvon) VALUES (".
|
||||
$this->db_add_param($this->mitarbeiter_uid).', '.
|
||||
$this->db_add_param($this->studiengang_kz, FHC_INTEGER).', '.
|
||||
$this->db_add_param($this->besqualcode, FHC_INTEGER).', '.
|
||||
$this->db_add_param($this->beginn).', '.
|
||||
$this->db_add_param($this->ende).', '.
|
||||
$this->db_add_param($this->updateamum).', '.
|
||||
$this->db_add_param($this->updatevon).', '.
|
||||
$this->db_add_param($this->insertamum).', '.
|
||||
$this->db_add_param($this->insertvon).');';
|
||||
$this->db_add_param($this->mitarbeiter_uid).', '.
|
||||
$this->db_add_param($this->studiengang_kz, FHC_INTEGER).', '.
|
||||
$this->db_add_param($this->besqualcode, FHC_INTEGER).', '.
|
||||
$this->db_add_param($this->beginn).', '.
|
||||
$this->db_add_param($this->ende).', '.
|
||||
$this->db_add_param($this->updateamum).', '.
|
||||
$this->db_add_param($this->updatevon).', '.
|
||||
$this->db_add_param($this->insertamum).', '.
|
||||
$this->db_add_param($this->insertvon).');';
|
||||
}
|
||||
else
|
||||
{
|
||||
if($this->studiengang_kz_old=='')
|
||||
$this->studiengang_kz_old = $this->studiengang_kz;
|
||||
|
||||
//Bestehenden Datensatz aktualisieren
|
||||
$qry= "UPDATE bis.tbl_entwicklungsteam SET".
|
||||
" besqualcode=".$this->db_add_param($this->besqualcode, FHC_INTEGER).",".
|
||||
" beginn=".$this->db_add_param($this->beginn).",".
|
||||
" studiengang_kz=".$this->db_add_param($this->studiengang_kz, FHC_INTEGER).",".
|
||||
" ende=".$this->db_add_param($this->ende).",".
|
||||
" updateamum=".$this->db_add_param($this->updateamum).",".
|
||||
" updatevon=".$this->db_add_param($this->updatevon).
|
||||
" WHERE entwicklungsteam_id=".$this->db_add_param($this->entwicklungsteam_id).";";
|
||||
" besqualcode=".$this->db_add_param($this->besqualcode, FHC_INTEGER).",".
|
||||
" beginn=".$this->db_add_param($this->beginn).",".
|
||||
" studiengang_kz=".$this->db_add_param($this->studiengang_kz, FHC_INTEGER).",".
|
||||
" ende=".$this->db_add_param($this->ende).",".
|
||||
" mitarbeiter_uid=".$this->db_add_param($this->mitarbeiter_uid).",".
|
||||
" updateamum=".$this->db_add_param($this->updateamum).",".
|
||||
" updatevon=".$this->db_add_param($this->updatevon).
|
||||
" WHERE entwicklungsteam_id=".$this->db_add_param($this->entwicklungsteam_id).";";
|
||||
}
|
||||
|
||||
if($this->db_query($qry))
|
||||
@@ -234,7 +257,7 @@ class entwicklungsteam extends basis_db
|
||||
if($studiengang_kz!=null)
|
||||
$qry.=" AND studiengang_kz=".$this->db_add_param($studiengang_kz);
|
||||
|
||||
$qry.=";";
|
||||
$qry.=";";
|
||||
|
||||
if($this->db_query($qry))
|
||||
{
|
||||
@@ -316,7 +339,7 @@ class entwicklungsteam extends basis_db
|
||||
|
||||
if ($sort != null)
|
||||
{
|
||||
$qry .= " ORDER BY ".$sort;
|
||||
$qry .= " ORDER BY ".$sort;
|
||||
}
|
||||
|
||||
$qry .= ";";
|
||||
@@ -364,7 +387,7 @@ class entwicklungsteam extends basis_db
|
||||
$bismeldung_jahr = $datetime->format('Y');
|
||||
|
||||
//laden des Datensatzes
|
||||
$qry = "SELECT tbl_entwicklungsteam.*, tbl_besqual.*, tbl_studiengang.studiengang_kz, tbl_studiengang.melderelevant
|
||||
$qry = "SELECT tbl_entwicklungsteam.*, tbl_besqual.*, tbl_studiengang.studiengang_kz, tbl_studiengang.melderelevant
|
||||
FROM bis.tbl_entwicklungsteam
|
||||
JOIN bis.tbl_besqual USING(besqualcode)
|
||||
JOIN public.tbl_studiengang USING(studiengang_kz)
|
||||
@@ -375,7 +398,7 @@ class entwicklungsteam extends basis_db
|
||||
if($studiengang_kz!=null)
|
||||
$qry.=" AND studiengang_kz=".$this->db_add_param($studiengang_kz);
|
||||
|
||||
$qry.=";";
|
||||
$qry.=";";
|
||||
|
||||
if($this->db_query($qry))
|
||||
{
|
||||
@@ -407,5 +430,20 @@ class entwicklungsteam extends basis_db
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Überprüft, ob die übergebene Uid in der Tabelle tbl_mitarbeiter vorhanden ist
|
||||
* @param String $uid UID des Mitarbeiters
|
||||
* @return true wenn vorhanden, false wenn nicht
|
||||
*/
|
||||
private function checkIfExistingUid($uid)
|
||||
{
|
||||
$mitarbeiter = new mitarbeiter();
|
||||
|
||||
if(!$mitarbeiter->checkIfExistingUid($uid))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -1995,5 +1995,23 @@ class mitarbeiter extends benutzer
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prueft ob eine existierende ma_uid übergeben wurde
|
||||
* @param String $mitarbeiter_uid mitarbeiter_uid
|
||||
* @return bool True wenn mitarbeiter_uid vorhanden sonst false.
|
||||
*/
|
||||
public function checkIfExistingUid($mitarbeiter_uid)
|
||||
{
|
||||
$qry = "Select 1 from public.tbl_mitarbeiter where mitarbeiter_uid =".$this->db_add_param($mitarbeiter_uid);
|
||||
|
||||
if ($result = $this->db_query($qry))
|
||||
{
|
||||
if ($this->db_num_rows($result) > 0)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -7,6 +7,11 @@ html {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
body.compat,
|
||||
body.compat main#cis-main {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.fhc-dark-bg{
|
||||
background-color: var(--fhc-dark);
|
||||
color:var(--fhc-light) !important;
|
||||
@@ -454,8 +459,8 @@ html {
|
||||
|
||||
/* desktop */
|
||||
@media (min-width: 992px) {
|
||||
body {
|
||||
display: flex;
|
||||
body.cis4 {
|
||||
display: flex;
|
||||
padding-top: var(--fhc-cis-header-height);
|
||||
width: calc(100svw - var(--scrollbar-width, 0px));
|
||||
/* overflow: visible !important; */
|
||||
@@ -760,7 +765,7 @@ html {
|
||||
}
|
||||
|
||||
.dashboard-item {
|
||||
overscroll-behavior: none;
|
||||
overscroll-behavior: auto;
|
||||
}
|
||||
|
||||
.abgabe-zieldatum-border-red,
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
/* the panel of js/directives/inViewTooltip.js. place() sets its place, max width and max height
|
||||
inside the viewport, so neither a scrolling container nor a modal body clips it */
|
||||
.fhc-in-view-tooltip {
|
||||
--fhc-in-view-tooltip-progress: var(--fhc-blue-primary, #0d6efd);
|
||||
position: fixed;
|
||||
z-index: 1080;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: max-content;
|
||||
border: 1px solid var(--bs-border-color, #dee2e6);
|
||||
border-radius: 0.375rem;
|
||||
background-color: var(--bs-body-bg, #fff);
|
||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
color: var(--bs-body-color, #212529);
|
||||
font-size: 0.875rem;
|
||||
font-weight: normal;
|
||||
line-height: 1.5;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* the dark primary reaches 2:1 on the dark panel, the highlight 3.9:1 */
|
||||
html.dark .fhc-in-view-tooltip {
|
||||
--fhc-in-view-tooltip-progress: var(--fhc-blue-primary-highlight, #6ea8fe);
|
||||
}
|
||||
|
||||
/* the body scrolls, so the progress border stays on the panel edge */
|
||||
.fhc-in-view-tooltip-body {
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
padding: 0.75rem 1rem;
|
||||
white-space: pre-line; /* the text keeps its line breaks */
|
||||
}
|
||||
|
||||
/* the hover timer of a sticky panel: after a pause the border fills up clockwise, then the panel sticks.
|
||||
The 2 px stroke covers the 1 px border and 1 px inside it */
|
||||
.fhc-in-view-tooltip-progress {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.fhc-in-view-tooltip-progress rect {
|
||||
fill: none;
|
||||
stroke: var(--fhc-in-view-tooltip-progress);
|
||||
stroke-width: 2;
|
||||
stroke-dasharray: var(--fhc-in-view-tooltip-perimeter);
|
||||
stroke-dashoffset: var(--fhc-in-view-tooltip-perimeter);
|
||||
}
|
||||
|
||||
.fhc-in-view-tooltip--holding .fhc-in-view-tooltip-progress rect {
|
||||
animation: fhc-in-view-tooltip-hold var(--fhc-in-view-tooltip-hold-fill) ease-in var(--fhc-in-view-tooltip-hold-delay) forwards;
|
||||
}
|
||||
|
||||
/* a click during the hover timer fills the rest of the border quickly */
|
||||
.fhc-in-view-tooltip--pinned .fhc-in-view-tooltip-progress rect {
|
||||
stroke-dashoffset: 0;
|
||||
transition: stroke-dashoffset 0.15s linear;
|
||||
}
|
||||
|
||||
@keyframes fhc-in-view-tooltip-hold {
|
||||
to {
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* the ? icon of components/InViewHelp.js takes the color of its context, e.g. white in the blue
|
||||
modal header. A class like text-muted overrides it */
|
||||
.fhc-in-view-help-btn,
|
||||
.fhc-in-view-help-btn:hover,
|
||||
.fhc-in-view-help-btn:focus-visible,
|
||||
.btn.fhc-in-view-help-btn:active {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.fhc-in-view-help-btn {
|
||||
line-height: 1;
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -23,11 +23,11 @@ export default {
|
||||
params: { ort_kurzbz: ort_kurbz }
|
||||
};
|
||||
},
|
||||
getRooms(datum, von, bis, typ, personenanzahl = 0, standort_id = null, advancedFilters = {}) {
|
||||
getRooms(datum, von, bis, typ, personenanzahl = 0) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/Ort/getRooms',
|
||||
params: { datum, von, bis, typ, personenanzahl, standort_id, ...advancedFilters }
|
||||
params: { datum, von, bis, typ, personenanzahl }
|
||||
};
|
||||
},
|
||||
getRoomTypes() {
|
||||
@@ -36,19 +36,5 @@ export default {
|
||||
url: '/api/frontend/v1/Ort/getTypes',
|
||||
params: { }
|
||||
};
|
||||
},
|
||||
getStandorte() {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/Ort/getStandorte',
|
||||
params: { }
|
||||
};
|
||||
},
|
||||
getAdvancedFilterOptions() {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/Ort/getAdvancedFilterOptions',
|
||||
params: { }
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -44,5 +44,15 @@ export default {
|
||||
studiensemester_kurzbz
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
getContainingOrNearestByDateRange(from, to) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/organisation/studiensemester/getContainingOrNearestByDateRange',
|
||||
params: {
|
||||
from,
|
||||
to,
|
||||
}
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -17,6 +17,8 @@ const router = VueRouter.createRouter({
|
||||
component: LVVerwaltung,
|
||||
children: [
|
||||
{ name: 'emp', path: 'emp/:emp/:treemenu(.*)*', component: LVVerwaltung },
|
||||
{ name: 'le', path: 'le/:le', component: LVVerwaltung },
|
||||
{ name: 'lv', path: 'lv/:lv', component: LVVerwaltung },
|
||||
{ name: 'treemenu', path: ':treemenu(.*)*', component: LVVerwaltung }
|
||||
]
|
||||
},
|
||||
|
||||
@@ -103,6 +103,13 @@ export default {
|
||||
keyboard: this.keyboard
|
||||
});
|
||||
},
|
||||
//to avoid pagelocks if more than one instance
|
||||
beforeUnmount() {
|
||||
if (this.modal) {
|
||||
this.modal.dispose();
|
||||
this.modal = null;
|
||||
}
|
||||
},
|
||||
popup(body, options, title, footer) {
|
||||
const BsModal = this,
|
||||
slots = {};
|
||||
|
||||
@@ -16,9 +16,15 @@ export default {
|
||||
return this.mode + '#' + this.path + '#' + this.query_string;
|
||||
}
|
||||
},
|
||||
beforeMount: function() {
|
||||
document.body.classList.add('compat');
|
||||
},
|
||||
mounted: function() {
|
||||
this.srcUrl = this.buildSrcUrl();
|
||||
},
|
||||
beforeUnmount: function() {
|
||||
document.body.classList.remove('compat');
|
||||
},
|
||||
watch: {
|
||||
propsWatchHelper: function() {
|
||||
if(this.lastLoadediFrameURL === '') {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import FhcCalendar from "../../Calendar/LvPlan.js";
|
||||
|
||||
import ApiLvPlan from '../../../api/factory/lvPlan.js';
|
||||
import ApiStudienSemester from '../../../api/factory/studiensemester.js';
|
||||
import ApiAuthinfo from '../../../api/factory/authinfo.js';
|
||||
|
||||
export const DEFAULT_MODE_LVPLAN_DESKTOP = "Week";
|
||||
@@ -16,13 +17,12 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
studiensemester_kurzbz: null,
|
||||
studiensemester_start: null,
|
||||
studiensemester_ende: null,
|
||||
uid: null,
|
||||
isMitarbeiter: false,
|
||||
isStudent: false,
|
||||
timezone: FHC_JS_DATA_STORAGE_OBJECT.timezone,
|
||||
startSemester: null,
|
||||
endSemester: null,
|
||||
};
|
||||
},
|
||||
inject: ["isMobile"],
|
||||
@@ -48,9 +48,12 @@ export default {
|
||||
return this.propsViewData?.mode;
|
||||
},
|
||||
downloadLinks() {
|
||||
if (!this.studiensemester_start || !this.studiensemester_ende || !this.uid)
|
||||
return false;
|
||||
const startDate = this.startSemester?.start;
|
||||
const endDate = this.endSemester ? this.endSemester.ende : this.startSemester?.ende;
|
||||
|
||||
if (!startDate || !endDate || !this.uid)
|
||||
return false;
|
||||
|
||||
let type = false;
|
||||
type = this.isStudent ? 'student' : type;
|
||||
type = this.isMitarbeiter ? 'lektor' : type;
|
||||
@@ -61,10 +64,10 @@ export default {
|
||||
|
||||
const opts = { zone: this.timezone };
|
||||
const start = luxon.DateTime
|
||||
.fromISO(this.studiensemester_start, opts)
|
||||
.fromISO(startDate, opts)
|
||||
.toUnixInteger();
|
||||
const ende = luxon.DateTime
|
||||
.fromISO(this.studiensemester_ende, opts)
|
||||
.fromISO(endDate, opts)
|
||||
.toUnixInteger();
|
||||
|
||||
const download_link = FHC_JS_DATA_STORAGE_OBJECT.app_root
|
||||
@@ -109,16 +112,21 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
updateRange(rangeInterval) {
|
||||
this.$api
|
||||
.call(ApiLvPlan.studiensemesterDateInterval(
|
||||
rangeInterval.end.startOf('week').toISODate()
|
||||
))
|
||||
.then(res => {
|
||||
this.studiensemester_kurzbz = res.data.studiensemester_kurzbz;
|
||||
this.studiensemester_start = res.data.start;
|
||||
this.studiensemester_ende = res.data.ende;
|
||||
});
|
||||
async updateRange(rangeInterval) {
|
||||
const semesterResponse = await this.$api.call(
|
||||
ApiStudienSemester.getContainingOrNearestByDateRange(
|
||||
rangeInterval.s.toISO().slice(0, 10),
|
||||
rangeInterval.e.toISO().slice(0, 10),
|
||||
),
|
||||
);
|
||||
this.startSemester =
|
||||
semesterResponse.data.length
|
||||
? semesterResponse.data[0]
|
||||
: null;
|
||||
this.endSemester =
|
||||
semesterResponse.data.length > 1
|
||||
? semesterResponse.data[1]
|
||||
: null;
|
||||
},
|
||||
getPromiseFunc(start, end) {
|
||||
return [
|
||||
@@ -142,8 +150,11 @@ export default {
|
||||
<div class="cis-lvplan-personal d-flex flex-column h-100">
|
||||
<h2>
|
||||
{{ $p.t('lehre/stundenplan') }}
|
||||
<span v-if="studiensemester_kurzbz" class="ps-3">
|
||||
{{ studiensemester_kurzbz }}
|
||||
<span v-if="startSemester?.studiensemester_kurzbz">
|
||||
{{ startSemester.studiensemester_kurzbz }}
|
||||
<span v-if="endSemester?.studiensemester_kurzbz">
|
||||
{{ "- " + endSemester.studiensemester_kurzbz }}
|
||||
</span>
|
||||
</span>
|
||||
</h2>
|
||||
<hr>
|
||||
|
||||
@@ -5,6 +5,7 @@ import FhcCalendar from "../../Calendar/LvPlan.js";
|
||||
import ApiLvPlan from "../.././../api/factory/lvPlan.js";
|
||||
import ApiOtherLvPlan from "../.././../api/factory/otherLvPlan.js";
|
||||
import ApiAuthinfo from "../../../api/factory/authinfo.js";
|
||||
import ApiStudienSemester from '../../../api/factory/studiensemester.js';
|
||||
|
||||
export const DEFAULT_MODE_LVPLAN_DESKTOP = "Week";
|
||||
export const DEFAULT_MODE_LVPLAN_MOBILE = "List";
|
||||
@@ -22,9 +23,6 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
localProps: {},
|
||||
studiensemester_kurzbz: null,
|
||||
studiensemester_start: null,
|
||||
studiensemester_ende: null,
|
||||
isOtherPersonMitarbeiter: false,
|
||||
isOtherPersonStudent: false,
|
||||
currentStgBezeichnung: null,
|
||||
@@ -36,6 +34,8 @@ export default {
|
||||
photo: "",
|
||||
},
|
||||
timezone: FHC_JS_DATA_STORAGE_OBJECT.timezone,
|
||||
startSemester: null,
|
||||
endSemester: null,
|
||||
};
|
||||
},
|
||||
inject: ["isMobile"],
|
||||
@@ -67,11 +67,10 @@ export default {
|
||||
// do not show download links in otherLvPlan Mode
|
||||
return false;
|
||||
|
||||
if (
|
||||
!this.studiensemester_start ||
|
||||
!this.studiensemester_ende ||
|
||||
!this.propsViewData.otherUid
|
||||
)
|
||||
const startDate = this.startSemester?.start;
|
||||
const endDate = this.endSemester ? this.endSemester.ende : this.startSemester?.ende;
|
||||
|
||||
if (!startDate || !endDate || !this.uid)
|
||||
return false;
|
||||
|
||||
const type = this.isOtherPersonStudent
|
||||
@@ -84,11 +83,11 @@ export default {
|
||||
|
||||
const opts = { zone: this.timezone };
|
||||
const start = luxon.DateTime.fromISO(
|
||||
this.studiensemester_start,
|
||||
startDate,
|
||||
opts,
|
||||
).toUnixInteger();
|
||||
const ende = luxon.DateTime.fromISO(
|
||||
this.studiensemester_ende,
|
||||
endDate,
|
||||
opts,
|
||||
).toUnixInteger();
|
||||
|
||||
@@ -166,19 +165,21 @@ export default {
|
||||
},
|
||||
});
|
||||
},
|
||||
updateRange(rangeInterval) {
|
||||
this.$api
|
||||
.call(
|
||||
ApiLvPlan.studiensemesterDateInterval(
|
||||
rangeInterval.end.startOf("week").toISODate(),
|
||||
),
|
||||
)
|
||||
.then((res) => {
|
||||
this.studiensemester_kurzbz =
|
||||
res.data.studiensemester_kurzbz;
|
||||
this.studiensemester_start = res.data.start;
|
||||
this.studiensemester_ende = res.data.ende;
|
||||
});
|
||||
async updateRange(rangeInterval) {
|
||||
const semesterResponse = await this.$api.call(
|
||||
ApiStudienSemester.getContainingOrNearestByDateRange(
|
||||
rangeInterval.s.toISO().slice(0, 10),
|
||||
rangeInterval.e.toISO().slice(0, 10),
|
||||
),
|
||||
);
|
||||
this.startSemester =
|
||||
semesterResponse.data.length
|
||||
? semesterResponse.data[0]
|
||||
: null;
|
||||
this.endSemester =
|
||||
semesterResponse.data.length > 1
|
||||
? semesterResponse.data[1]
|
||||
: null;
|
||||
},
|
||||
getPromiseFunc(start, end) {
|
||||
return [
|
||||
@@ -250,8 +251,14 @@ export default {
|
||||
<div class="d-flex flex-column h-100">
|
||||
<h2>
|
||||
<div class="d-flex flex-row justify-content-between align-items-center">
|
||||
<span>
|
||||
{{ $p.t('lehre/stundenplan') + (studiensemester_kurzbz ? " " + studiensemester_kurzbz : "") }}
|
||||
<span>
|
||||
{{ $p.t('lehre/stundenplan') }}
|
||||
<span v-if="startSemester?.studiensemester_kurzbz">
|
||||
{{ startSemester.studiensemester_kurzbz }}
|
||||
<span v-if="endSemester?.studiensemester_kurzbz">
|
||||
{{ "- " + endSemester.studiensemester_kurzbz }}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<div @click="this.$router.push({name: 'ProfilView', params: {uid: propsViewData.otherUid}})" type="button" class="d-flex flex-row align-items-center gap-3">
|
||||
<span v-if="otherPersonData.fullNameWithTitle?.length">
|
||||
|
||||
@@ -73,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)
|
||||
.then(data => data.data)
|
||||
.then(data => data.data?.retval)
|
||||
.then(res => {
|
||||
if (Array.isArray(res) && res.length > 0) {
|
||||
this.hasLvPlanEintraege = true;
|
||||
@@ -114,4 +114,4 @@ export default {
|
||||
|
||||
|
||||
`,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,37 +1,11 @@
|
||||
import {CoreFilterCmpt} from "../../../components/filter/Filter.js";
|
||||
import VueDatePicker from '../../vueDatepicker.js.php';
|
||||
import ApiOrt from '../../../api/factory/ort.js'
|
||||
import InViewHelp from '../../InViewHelp.js';
|
||||
|
||||
// advanced filters: tbl_ort column, filter type and label, the same list as Ort::ADVANCED_FILTERS
|
||||
const ADVANCED_FILTERS = [
|
||||
{ column: 'lehre', type: 'flag', label: 'rauminfo/nurLehrraeume', checked: true },
|
||||
{ column: 'content_id', type: 'exists', label: 'rauminfo/nurMitInfoseite' },
|
||||
{ column: 'ort_kurzbz', type: 'text', label: 'rauminfo/raum_kurzbz' },
|
||||
{ column: 'bezeichnung', type: 'text', label: 'global/bezeichnung' },
|
||||
{ column: 'planbezeichnung', type: 'text', label: 'rauminfo/raumnummer' },
|
||||
{ column: 'stockwerk', type: 'range', label: 'rauminfo/stockwerk' },
|
||||
{ column: 'gebteil', type: 'select', label: 'rauminfo/gebaeudeteil' },
|
||||
{ column: 'm2', type: 'range', label: 'rauminfo/flaeche' }
|
||||
];
|
||||
|
||||
// the GET parameters of the advanced filters with their default values
|
||||
const ADVANCED_DEFAULTS = {};
|
||||
for (const { column, type, checked } of ADVANCED_FILTERS) {
|
||||
if (type === 'range') {
|
||||
ADVANCED_DEFAULTS[column + '_min'] = '';
|
||||
ADVANCED_DEFAULTS[column + '_max'] = '';
|
||||
} else {
|
||||
ADVANCED_DEFAULTS[column] = type === 'flag' || type === 'exists' ? !!checked : '';
|
||||
}
|
||||
}
|
||||
|
||||
export const Raumsuche = {
|
||||
name: "Raumsuche",
|
||||
components: {
|
||||
VueDatePicker,
|
||||
CoreFilterCmpt,
|
||||
InViewHelp,
|
||||
InputNumber: primevue.inputnumber,
|
||||
},
|
||||
inject: ["isMobile"],
|
||||
@@ -43,16 +17,12 @@ export const Raumsuche = {
|
||||
tableBuiltResolve: null,
|
||||
tableBuiltPromise: null,
|
||||
roomtypes: null,
|
||||
defaultType: {
|
||||
raumtyp_kurzbz: '',
|
||||
beschreibung: Vue.computed(() => this.$p.t('global/alle'))
|
||||
},
|
||||
anzahl: 1,
|
||||
selectedType: null,
|
||||
standorte: null,
|
||||
standort_id: null,
|
||||
roomsRequest: 0,
|
||||
loading: false,
|
||||
advancedFilterFields: ADVANCED_FILTERS,
|
||||
advancedFilters: { ...ADVANCED_DEFAULTS },
|
||||
advancedFilterOptions: {},
|
||||
showAdvancedFilters: false,
|
||||
datum: new Date(),
|
||||
von: Vue.ref({
|
||||
hours: new Date().getHours(),
|
||||
@@ -85,12 +55,7 @@ export const Raumsuche = {
|
||||
{title: Vue.computed(() => this.$p.t('rauminfo/raum_kurzbz')), field: 'ort_kurzbz', widthGrow: 1},
|
||||
{title: Vue.computed(() => this.$p.t('global/bezeichnung')), field: 'bezeichnung', widthGrow: 2},
|
||||
{title: Vue.computed(() => this.$p.t('rauminfo/raumnummer')), field: 'nummer', widthGrow: 1},
|
||||
{title: Vue.computed(() => this.$p.t('rauminfo/standort')), field: 'standort', widthGrow: 1},
|
||||
{title: Vue.computed(() => this.$p.t('rauminfo/gebaeudeteil')), field: 'gebteil', widthGrow: 1, visible: false},
|
||||
{title: Vue.computed(() => this.$p.t('rauminfo/stockwerk')), field: 'stockwerk', sorter: 'number', widthGrow: 1, visible: false},
|
||||
{title: Vue.computed(() => this.$p.t('rauminfo/personcap')), field: 'personen', widthGrow: 1},
|
||||
{title: Vue.computed(() => this.$p.t('rauminfo/flaeche')), field: 'm2', sorter: 'number', widthGrow: 1, visible: false},
|
||||
{title: Vue.computed(() => this.$p.t('rauminfo/lehrraum')), field: 'lehre', formatter: 'tickCross', hozAlign: 'center', widthGrow: 1, visible: false},
|
||||
{title: Vue.computed(() => this.$p.t('rauminfo/rauminfo')),
|
||||
field: 'linkInfo', formatter: this.linkFormatter, widthGrow: 1},
|
||||
{title: Vue.computed(() => this.$p.t('rauminfo/roomReservations')),
|
||||
@@ -109,21 +74,8 @@ export const Raumsuche = {
|
||||
computed: {
|
||||
isDarkMode(){
|
||||
return this.$theme.theme_name.value == 'dark';
|
||||
},
|
||||
// shown on the toggle, the panel can be closed while its filters are active
|
||||
activeAdvancedFilters() {
|
||||
return Object.keys(ADVANCED_DEFAULTS).filter(key => this.advancedFilters[key] !== ADVANCED_DEFAULTS[key]).length
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
datum: 'loadRooms',
|
||||
von: 'loadRooms',
|
||||
bis: 'loadRooms',
|
||||
selectedType: 'loadRooms',
|
||||
standort_id: 'loadRooms',
|
||||
anzahl: 'loadRooms',
|
||||
advancedFilters: { handler: 'loadRooms', deep: true }
|
||||
},
|
||||
methods: {
|
||||
tableResolve(resolve) {
|
||||
this.tableBuiltResolve = resolve
|
||||
@@ -163,12 +115,7 @@ export const Raumsuche = {
|
||||
ort_kurzbz: room.ort_kurzbz,
|
||||
bezeichnung: room.bezeichnung.replace('&', '&'),
|
||||
nummer: room.planbezeichnung,
|
||||
standort: room.standort,
|
||||
gebteil: room.gebteil,
|
||||
stockwerk: room.stockwerk,
|
||||
personen: room.max_person,
|
||||
m2: room.m2,
|
||||
lehre: room.lehre,
|
||||
linkInfo: room.content_id ? this.roomInfoLink(room) : null,
|
||||
linkRes: this.roomPlanLink(room)
|
||||
|
||||
@@ -184,40 +131,21 @@ export const Raumsuche = {
|
||||
res?.data?.forEach(type => {
|
||||
type.beschreibung = type.beschreibung.replace('&', '&')
|
||||
})
|
||||
this.selectedType = this.defaultType
|
||||
this.roomtypes = res?.data ?? []
|
||||
})
|
||||
},
|
||||
loadStandorte() {
|
||||
this.$api.call(ApiOrt.getStandorte())
|
||||
.then(res => {
|
||||
this.standorte = res?.data ?? []
|
||||
})
|
||||
},
|
||||
loadAdvancedFilterOptions() {
|
||||
this.$api.call(ApiOrt.getAdvancedFilterOptions())
|
||||
.then(res => {
|
||||
this.advancedFilterOptions = res?.data ?? {}
|
||||
})
|
||||
},
|
||||
loadRooms() {
|
||||
// only the latest request may fill the table, older answers can arrive later
|
||||
const request = ++this.roomsRequest
|
||||
this.loading = true
|
||||
// empty and unchecked filters stay out of the request
|
||||
const advancedFilters = Object.fromEntries(Object.entries(this.advancedFilters).filter(([, value]) => value !== '' && value !== false))
|
||||
this.$api.call(ApiOrt.getRooms(this.datum.toISOString(), this.getTimeString(this.von), this.getTimeString(this.bis), this.selectedType?.raumtyp_kurzbz ?? '', this.anzahl, this.standort_id, advancedFilters))
|
||||
this.$api.call(ApiOrt.getRooms(this.datum.toISOString(), this.getTimeString(this.von), this.getTimeString(this.bis), this.selectedType?.raumtyp_kurzbz ?? '', this.anzahl))
|
||||
.then(res => {
|
||||
if(request === this.roomsRequest && res?.data?.retval) this.setupData(res.data.retval)
|
||||
})
|
||||
.finally(() => {
|
||||
if(request === this.roomsRequest) this.loading = false
|
||||
})
|
||||
if(res?.data?.retval) this.setupData(res.data.retval)
|
||||
})
|
||||
},
|
||||
handleUuidDefined(uuid) {
|
||||
this.tabulatorUuid = uuid
|
||||
},
|
||||
resetAdvancedFilters() {
|
||||
this.advancedFilters = { ...ADVANCED_DEFAULTS }
|
||||
search(){
|
||||
this.loadRooms()
|
||||
},
|
||||
setRoute(val) {
|
||||
// TODO: router push
|
||||
@@ -239,8 +167,6 @@ export const Raumsuche = {
|
||||
await this.tableBuiltPromise
|
||||
|
||||
this.loadRoomTypes()
|
||||
this.loadStandorte()
|
||||
this.loadAdvancedFilterOptions()
|
||||
this.loadRooms()
|
||||
|
||||
const tableID = this.tabulatorUuid ? ('-' + this.tabulatorUuid) : ''
|
||||
@@ -256,17 +182,14 @@ export const Raumsuche = {
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.phrasenPromise = this.$p.loadCategory(['rauminfo', 'global', 'ui', 'filter'])
|
||||
this.phrasenPromise = this.$p.loadCategory(['rauminfo', 'global'])
|
||||
this.phrasenPromise.then(()=> {this.phrasenResolved = true})
|
||||
},
|
||||
mounted() {
|
||||
this.setupMounted()
|
||||
},
|
||||
template: `
|
||||
<div class="d-flex align-items-center gap-2 mb-2">
|
||||
<h1 class="h3 mb-0">{{$p.t('rauminfo/roomSearch')}}</h1>
|
||||
<in-view-help button-class="fs-5 text-body-secondary" :text="$p.t('rauminfo/tooltipRaumsuchev2')"></in-view-help>
|
||||
</div>
|
||||
<h1 class="h3">{{$p.t('rauminfo/roomSearch')}}</h1>
|
||||
<hr>
|
||||
<div class="row">
|
||||
<div :class="{'pb-1': isMobile}" class="col-12 col-lg-2">
|
||||
@@ -312,92 +235,38 @@ export const Raumsuche = {
|
||||
</VueDatePicker>
|
||||
</div>
|
||||
|
||||
<div :class="{'pb-1': isMobile}" class="col-12 col-lg-2">
|
||||
<div :class="{'pb-1': isMobile}" class="col-12 col-lg-3">
|
||||
<select ref="raumtyp" id="raumtypSelect" v-model="selectedType" class="form-select"
|
||||
:aria-label="$p.t('rauminfo/raumtyp')" @change="setRoute($event.target.value)">
|
||||
<option :value="null">{{ $p.t('global/alle') }}</option>
|
||||
:aria-label="$p.t('global/studiensemester_auswaehlen')" @change="setRoute($event.target.value)">
|
||||
<option :key="defaultType" selected :value="defaultType">{{defaultType.beschreibung}}</option>
|
||||
<option v-for="typ in roomtypes" :key="typ" :value="typ">{{typ.beschreibung}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div :class="{'pb-1': isMobile}" class="col-12 col-lg-2">
|
||||
<select id="standortSelect" v-model="standort_id" class="form-select" :aria-label="$p.t('rauminfo/standort')">
|
||||
<option :value="null">{{ $p.t('rauminfo/alleStandorte') }}</option>
|
||||
<option value="none">{{ $p.t('rauminfo/ohneStandort') }}</option>
|
||||
<option v-for="standort in standorte" :key="standort.standort_id" :value="standort.standort_id">{{ standort.bezeichnung }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
<div :class="{'pb-2': isMobile}" class="col-12 col-lg">
|
||||
<div :class="{'pb-2': isMobile}" class="col-12 col-lg-3">
|
||||
<InputNumber v-model="anzahl"
|
||||
:prefix="$p.t('rauminfo/minCapacity') + ': '"
|
||||
inputId="anzahlInput" :min="1" :max="1000"
|
||||
:style="{'width': '100%'}"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-12 col-lg-2">
|
||||
<button class="btn btn-primary border-0" @click="search">{{ $p.t('rauminfo/roomSearch') }} <i class="fa fa-magnifying-glass"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="d-flex justify-content-end my-1">
|
||||
<button type="button" class="btn btn-link btn-sm p-0 text-decoration-none text-body-secondary"
|
||||
:aria-expanded="showAdvancedFilters" aria-controls="raumsucheAdvancedFilters"
|
||||
@click="showAdvancedFilters = !showAdvancedFilters">
|
||||
<i class="fa-solid fa-sliders me-1"></i>{{ $p.t('rauminfo/erweiterteFilter') }}
|
||||
<span v-if="activeAdvancedFilters" class="badge rounded-pill bg-primary ms-1">{{ activeAdvancedFilters }}</span>
|
||||
<i class="fa-solid ms-1" :class="showAdvancedFilters ? 'fa-chevron-up' : 'fa-chevron-down'"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div v-show="showAdvancedFilters" id="raumsucheAdvancedFilters" class="row g-2 mb-3">
|
||||
<div v-for="filter in advancedFilterFields" :key="filter.column" class="col-12 col-md-6 col-lg-3"
|
||||
:class="{'align-self-end': filter.type === 'flag' || filter.type === 'exists'}">
|
||||
<div v-if="filter.type === 'flag' || filter.type === 'exists'" class="form-check">
|
||||
<input :id="'raumsucheFilter-' + filter.column" v-model="advancedFilters[filter.column]" type="checkbox" class="form-check-input">
|
||||
<label :for="'raumsucheFilter-' + filter.column" class="form-check-label">{{ $p.t(filter.label) }}</label>
|
||||
</div>
|
||||
<template v-else>
|
||||
<label :for="'raumsucheFilter-' + filter.column" class="form-label small mb-1">{{ $p.t(filter.label) }}</label>
|
||||
<input v-if="filter.type === 'text'" :id="'raumsucheFilter-' + filter.column"
|
||||
v-model.lazy.trim="advancedFilters[filter.column]" type="text" class="form-control form-control-sm">
|
||||
<select v-else-if="filter.type === 'select'" :id="'raumsucheFilter-' + filter.column"
|
||||
v-model="advancedFilters[filter.column]" class="form-select form-select-sm">
|
||||
<option value="">{{ $p.t('ui/alle') }}</option>
|
||||
<option v-for="option in advancedFilterOptions[filter.column]" :key="option" :value="option">{{ option }}</option>
|
||||
</select>
|
||||
<div v-else class="input-group input-group-sm">
|
||||
<input :id="'raumsucheFilter-' + filter.column" v-model.lazy="advancedFilters[filter.column + '_min']"
|
||||
type="number" step="any" class="form-control"
|
||||
:placeholder="$p.t('ui/von')" :aria-label="$p.t(filter.label) + ' ' + $p.t('ui/von')">
|
||||
<input v-model.lazy="advancedFilters[filter.column + '_max']"
|
||||
type="number" step="any" class="form-control"
|
||||
:placeholder="$p.t('global/bis')" :aria-label="$p.t(filter.label) + ' ' + $p.t('global/bis')">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="col-12 text-end">
|
||||
<button type="button" class="btn btn-link btn-sm p-0" :disabled="!activeAdvancedFilters" @click="resetAdvancedFilters">
|
||||
{{ $p.t('filter/filterDelete') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="position-relative">
|
||||
<div :class="{'opacity-50': loading}">
|
||||
<core-filter-cmpt
|
||||
v-if="phrasenResolved"
|
||||
@uuidDefined="handleUuidDefined"
|
||||
:title="''"
|
||||
ref="raumsucheTable"
|
||||
:tabulator-options="raumsucheTableOptions"
|
||||
:tabulator-events="raumsucheTableEventHandlers"
|
||||
tableOnly
|
||||
:sideMenu="false"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="loading" class="position-absolute top-0 start-0 w-100 h-100 d-flex justify-content-center align-items-center">
|
||||
<i class="fa-solid fa-spinner fa-pulse fa-3x"></i>
|
||||
</div>
|
||||
</div>
|
||||
<core-filter-cmpt
|
||||
v-if="phrasenResolved"
|
||||
@uuidDefined="handleUuidDefined"
|
||||
:title="''"
|
||||
ref="raumsucheTable"
|
||||
:tabulator-options="raumsucheTableOptions"
|
||||
:tabulator-events="raumsucheTableEventHandlers"
|
||||
tableOnly
|
||||
:sideMenu="false"
|
||||
/>
|
||||
`,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
|
||||
export default {
|
||||
name: 'Cis-Renderer-Feiertag-CalendarEvent',
|
||||
props: {
|
||||
event: {
|
||||
type: Object,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { formatDate } from "../../../../helpers/DateHelpers.js"
|
||||
|
||||
export default {
|
||||
name: 'Cis-Renderer-Feiertag-ModalContent',
|
||||
props:{
|
||||
event: {
|
||||
type: Object,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export default {
|
||||
name: 'Cis-Renderer-Feiertag-ModalTitle',
|
||||
props:{
|
||||
event: {
|
||||
type: Object,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export default {
|
||||
name: 'Cis-Renderer-Lehreinheit-CalendarEvent',
|
||||
props:{
|
||||
event: {
|
||||
type: Object,
|
||||
|
||||
@@ -5,6 +5,7 @@ import ApiLvPlan from '../../../../api/factory/lvPlan.js';
|
||||
import ApiAddons from '../../../../api/factory/addons.js';
|
||||
|
||||
export default {
|
||||
name: 'Cis-Renderer-Lehreinheit-ModalContent',
|
||||
components:{
|
||||
LvMenu,
|
||||
},
|
||||
@@ -49,6 +50,16 @@ export default {
|
||||
return this.event.ende;
|
||||
}
|
||||
return numberPadding(this.event.ende.getHours()) + ":" + numberPadding(this.event.ende.getMinutes());
|
||||
},
|
||||
showLvMenu: function() {
|
||||
const hideOnRoutes = [
|
||||
'OtherLvPlan',
|
||||
'StgOrgLvPlan'
|
||||
];
|
||||
if(hideOnRoutes.includes(this.$route.name)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -162,6 +173,6 @@ export default {
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<lv-menu v-if="lvMenu.length && $route.name === 'MyLvPlan'" :containerStyles="['p-0']" :rowStyles="['m-0']" :menu="lvMenu" />
|
||||
<lv-menu v-if="lvMenu.length && showLvMenu" :containerStyles="['p-0']" :rowStyles="['m-0']" :menu="lvMenu" />
|
||||
</div>`,
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
export default {
|
||||
name: 'Cis-Renderer-Lehreinheit-ModalTitle',
|
||||
props:{
|
||||
event: {
|
||||
type: Object,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export default {
|
||||
name: 'Cis-Renderer-Reservierung-CalendarEvent',
|
||||
props: {
|
||||
event: {
|
||||
type: Object,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { numberPadding, formatDate } from "../../../../helpers/DateHelpers.js"
|
||||
import LvMenu from "../../Mylv/LvMenu.js";
|
||||
|
||||
export default {
|
||||
name: 'Cis-Renderer-Reservierung-ModalContent',
|
||||
props:{
|
||||
event: {
|
||||
type: Object,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export default {
|
||||
name: 'Cis-Renderer-Reservierung-ModalTitle',
|
||||
props:{
|
||||
event: {
|
||||
type: Object,
|
||||
|
||||
@@ -22,7 +22,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
pickerWidgets() {
|
||||
return this.widgets.filter(widget => widget.allowed);
|
||||
return this.widgets.filter(widget => widget.allowed).map(widget => ({ ...widget, permitted: true }));
|
||||
},
|
||||
sizeLimits() {
|
||||
return Object.fromEntries(this.widgets.map(({ setup, widget_id: type }) => {
|
||||
|
||||
@@ -6,10 +6,16 @@ export default {
|
||||
BsModal,
|
||||
WidgetIcon,
|
||||
},
|
||||
props: [
|
||||
"widgets",
|
||||
"hiddenWidgets"
|
||||
],
|
||||
props: {
|
||||
widgets: {
|
||||
type: [ Array, null ],
|
||||
required: true
|
||||
},
|
||||
hiddenWidgets: {
|
||||
type: Array,
|
||||
default: []
|
||||
}
|
||||
},
|
||||
data: () => ({
|
||||
callbacks: {}
|
||||
}),
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import InViewTooltip from '../directives/inViewTooltip.js';
|
||||
|
||||
/**
|
||||
* ? icon with a help text in the in-view tooltip. The panel stays inside the viewport, also at the
|
||||
* screen edge, in a modal and on a phone: a plain tooltip ran out of the screen there.
|
||||
*
|
||||
* Help texts are long, so the panel is sticky by default: it sticks after a hover of 1.4 s or a click,
|
||||
* a tap toggles it on touch. sticky: false makes it a plain hover tooltip.
|
||||
*
|
||||
* The view needs public/css/components/inViewTooltip.css.
|
||||
*
|
||||
* Usage: <in-view-help :text="$p.t('category/phrase')" button-class="fs-5 text-body-secondary"></in-view-help>
|
||||
*/
|
||||
export default {
|
||||
name: 'InViewHelp',
|
||||
directives: {
|
||||
tooltip: InViewTooltip
|
||||
},
|
||||
props: {
|
||||
text: { type: String, default: '' },
|
||||
sticky: { type: Boolean, default: true },
|
||||
buttonClass: { type: [String, Array, Object], default: '' }
|
||||
},
|
||||
template: /*html*/ `
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-link p-0 fhc-in-view-help-btn"
|
||||
:class="buttonClass"
|
||||
:aria-label="$p.t('ui/hilfe')"
|
||||
v-tooltip="{ value: text, sticky }"
|
||||
>
|
||||
<i class="fa fa-circle-question" aria-hidden="true"></i>
|
||||
</button>`
|
||||
};
|
||||
@@ -61,6 +61,7 @@ export default {
|
||||
selected: [],
|
||||
studiengang_kz: null,
|
||||
selectedStudiensemester: this.defaultSemester,
|
||||
preselected: null,
|
||||
dropdowns: {
|
||||
studiensemester_array: [],
|
||||
sprachen_array: [],
|
||||
@@ -73,7 +74,11 @@ export default {
|
||||
calcheightonly: true,
|
||||
types: [
|
||||
"mitarbeiter",
|
||||
"mitarbeiter_ohne_zuordnung"
|
||||
"mitarbeiter_ohne_zuordnung",
|
||||
/*
|
||||
"lehreinheit",
|
||||
"lehrveranstaltung",
|
||||
*/
|
||||
],
|
||||
actions: {
|
||||
employee: {
|
||||
@@ -86,6 +91,26 @@ export default {
|
||||
childactions: [
|
||||
]
|
||||
},
|
||||
lehreinheit: {
|
||||
defaultaction: {
|
||||
type: "function",
|
||||
action: (data) => {
|
||||
this.onSelectLehreinheit(data);
|
||||
}
|
||||
},
|
||||
childactions: [
|
||||
]
|
||||
},
|
||||
lehrveranstaltung: {
|
||||
defaultaction: {
|
||||
type: "function",
|
||||
action: (data) => {
|
||||
this.onSelectLehrveranstaltung(data);
|
||||
}
|
||||
},
|
||||
childactions: [
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -94,6 +119,8 @@ export default {
|
||||
filter() {
|
||||
let filter = {
|
||||
emp: this.$route.params.emp,
|
||||
le: this.$route.params.le,
|
||||
lv: this.$route.params.lv,
|
||||
studiensemester_kurzbz: this.selectedStudiensemester
|
||||
};
|
||||
let index;
|
||||
@@ -105,7 +132,7 @@ export default {
|
||||
if (index > -1)
|
||||
filter.semester = this.$route.params.treemenu[index+1];
|
||||
}
|
||||
filter.activeFilter = filter.emp ? 'employee' : filter.stg ? 'verband' : null;
|
||||
filter.activeFilter = filter.emp ? 'employee' : filter.stg ? 'verband' : filter.le ? 'lehreinheit' : filter.lv ? 'lehrveranstaltung' : null;
|
||||
return filter;
|
||||
},
|
||||
emp() {
|
||||
@@ -203,20 +230,50 @@ export default {
|
||||
},
|
||||
onSelectEmployee(emp)
|
||||
{
|
||||
let params = { ...this.$route.params }
|
||||
delete params.le;
|
||||
delete params.lv;
|
||||
|
||||
this.$router.push({
|
||||
name: 'emp',
|
||||
params: {
|
||||
...this.$route.params,
|
||||
...params,
|
||||
emp
|
||||
}
|
||||
});
|
||||
},
|
||||
onSelectLehreinheit(lehreinheit)
|
||||
{
|
||||
this.$router.push({
|
||||
name: 'le',
|
||||
params: {
|
||||
le: lehreinheit.id,
|
||||
stdsem: lehreinheit.studiensemester_kurzbz.toLowerCase()
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onSelectLehrveranstaltung(lehrveranstaltung)
|
||||
{
|
||||
this.$router.push({
|
||||
name: 'lv',
|
||||
params: {
|
||||
lv: lehrveranstaltung.id,
|
||||
stdsem: lehrveranstaltung?.studiensemester_kurzbz?.toLowerCase() ?? this.selectedStudiensemester.toLowerCase()
|
||||
}
|
||||
});
|
||||
},
|
||||
onSelectVerband({ path: link, stg_kz })
|
||||
{
|
||||
|
||||
let params = { ...this.$route.params }
|
||||
delete params.le;
|
||||
delete params.lv;
|
||||
|
||||
this.$router.push({
|
||||
name: this.$route.name == 'emp' ? 'emp' : 'treemenu',
|
||||
params: {
|
||||
...this.$route.params,
|
||||
...params,
|
||||
treemenu: link.split('/')
|
||||
}
|
||||
});
|
||||
@@ -295,7 +352,7 @@ export default {
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (!this.$route.params.stdsem) {
|
||||
if (this.$route.name !== 'le' && this.$route.name !== 'lv' && !this.$route.params.stdsem) {
|
||||
this.$router.replace({
|
||||
name: 'stdsem',
|
||||
params: {
|
||||
@@ -477,7 +534,8 @@ export default {
|
||||
<template #top>
|
||||
<lv-table ref="lvTable"
|
||||
v-model:selected="selected"
|
||||
@row-clicked="handleRowClicked"
|
||||
@row-clicked="handleRowClicked"
|
||||
@select-LV="onSelectLehrveranstaltung"
|
||||
:filter="filter"
|
||||
>
|
||||
<template #filterzuruecksetzen v-if="filter.activeFilter === 'employee'">
|
||||
|
||||
@@ -57,7 +57,8 @@ export default {
|
||||
},
|
||||
emits: [
|
||||
'update:selected',
|
||||
'row-clicked'
|
||||
'row-clicked',
|
||||
'selectLV',
|
||||
],
|
||||
watch: {
|
||||
filter: {
|
||||
@@ -106,8 +107,24 @@ export default {
|
||||
{
|
||||
event: 'dataProcessed',
|
||||
handler: (data) => {
|
||||
this.reexpandRows()
|
||||
this.$emit('update:selected', {})
|
||||
if (this.$route.params.le)
|
||||
{
|
||||
this.$refs.table.tabulator.selectRow(this.$refs.table.tabulator.getRows());
|
||||
}
|
||||
else if (this.$route.params.lv)
|
||||
{
|
||||
this.$refs.table.tabulator.selectRow(this.$refs.table.tabulator.getRows());
|
||||
|
||||
this.allRows = this.getAllRows(this.$refs.table.tabulator.getRows());
|
||||
this.allRows.forEach(row => {
|
||||
row.treeExpand()
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
this.reexpandRows()
|
||||
this.$emit('update:selected', {})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -221,35 +238,35 @@ export default {
|
||||
selectableRows: true,
|
||||
rowContextMenu: (component, e) => {
|
||||
|
||||
if (e.getData()?.lehreinheit_id === undefined)
|
||||
return;
|
||||
if (e.getData()?.lehreinheit_id === undefined || !e.getData()?.lehreinheit_id)
|
||||
return [];
|
||||
return [
|
||||
{
|
||||
label: "LV-Teil kopieren",
|
||||
label: this.$p.t('lehre', 'lvteilkopieren'),
|
||||
menu: [
|
||||
{
|
||||
label: "Alles",
|
||||
label: this.$p.t('lehre', 'alles'),
|
||||
action: (e, row) =>
|
||||
{
|
||||
this.copyLehreinheit(row, "alle");
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Nur LV-Teil",
|
||||
label: this.$p.t('lehre', 'nurlvteil'),
|
||||
action: (e, row) =>
|
||||
{
|
||||
this.copyLehreinheit(row, "lvteil");
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Nur mit Gruppen",
|
||||
label: this.$p.t('lehre', 'nurmitgruppen'),
|
||||
action: (e, row) =>
|
||||
{
|
||||
this.copyLehreinheit(row, "gruppen");
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Nur mit Lehrenden",
|
||||
label: this.$p.t('lehre', 'nurmitlehrende'),
|
||||
action: (e, row) =>
|
||||
{
|
||||
this.copyLehreinheit(row, "lektoren");
|
||||
@@ -258,7 +275,21 @@ export default {
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Entfernen",
|
||||
label: this.$p.t('lehre', 'zurlvspringen'),
|
||||
action: (e, row) => {
|
||||
this.openLV(row)
|
||||
},
|
||||
},
|
||||
{
|
||||
label: this.$p.t('lehre', 'lvteillinkkopieren'),
|
||||
action: (e, row) => {
|
||||
let rowData = row.getData();
|
||||
let link = FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/LVVerwaltung/stdsem/' + rowData.studiensemester_kurzbz.toLowerCase() + '/le/' + rowData.lehreinheit_id;
|
||||
navigator.clipboard.writeText(link);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: this.$p.t('ui', 'entfernen'),
|
||||
action: (e, row) => {
|
||||
this.deleteLehreinheit(row)
|
||||
},
|
||||
@@ -355,7 +386,7 @@ export default {
|
||||
},
|
||||
buildApiUrl()
|
||||
{
|
||||
if (!['emp', 'treemenu'].includes(this.$route.name))
|
||||
if (!['emp', 'treemenu', 'le', 'lv'].includes(this.$route.name))
|
||||
return null;
|
||||
|
||||
let url = 'stdsem/' + this.$route.params.stdsem;
|
||||
@@ -364,6 +395,14 @@ export default {
|
||||
url += '/emp/' + this.$route.params.emp;
|
||||
}
|
||||
|
||||
if (this.$route.params.le) {
|
||||
url += '/le/' + this.$route.params.le;
|
||||
}
|
||||
|
||||
if (this.$route.params.lv) {
|
||||
url += '/lv/' + this.$route.params.lv;
|
||||
}
|
||||
|
||||
if (this.$route.params.treemenu) {
|
||||
url += '/' + this.$route.params.treemenu.join('/');
|
||||
}
|
||||
@@ -502,6 +541,17 @@ export default {
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
openLV(row)
|
||||
{
|
||||
let rowData = row.getData();
|
||||
|
||||
|
||||
let data = {
|
||||
id: rowData.lehrveranstaltung_id,
|
||||
studiensemester_kurzbz: rowData.studiensemester_kurzbz
|
||||
}
|
||||
this.$emit('selectLV', data);
|
||||
},
|
||||
getAllRows(rows)
|
||||
{
|
||||
let result = [];
|
||||
|
||||
@@ -263,7 +263,7 @@ export default {
|
||||
this.$refs[modalRef].hide();
|
||||
},
|
||||
reload() {
|
||||
this.$refs.table.reloadTable();
|
||||
this.$refs.table?.reloadTable();
|
||||
},
|
||||
searchFirma(event) {
|
||||
if (this.abortController.firmen) {
|
||||
|
||||
+15
-2
@@ -30,6 +30,9 @@ export default {
|
||||
computed: {
|
||||
betreuerFormOpened() {
|
||||
return this.newMode || this.editMode;
|
||||
},
|
||||
isMitarbeiter(){
|
||||
return this.autocompleteSelectedBetreuer?.name?.includes('Mitarbeiter') ?? false;
|
||||
}
|
||||
},
|
||||
props: {
|
||||
@@ -430,6 +433,11 @@ export default {
|
||||
},
|
||||
actionKontaktdatenBearbeiten() {
|
||||
if (!this.autocompleteSelectedBetreuer) return;
|
||||
if (this.isMitarbeiter)
|
||||
{
|
||||
this.$fhcAlert.alertError(this.$p.t('ui', 'editingMitarbeiterPVonly'));
|
||||
return;
|
||||
}
|
||||
this.$refs.kontaktdatenModal.show();
|
||||
},
|
||||
// stuff to do after new person has been saved
|
||||
@@ -494,7 +502,12 @@ export default {
|
||||
<button class="btn btn-outline-secondary" @click="actionNewPerson">{{ $p.t('projektarbeit', 'neuePersonAnlegen') }}</button>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<button class="btn btn-outline-secondary float-end" @click="actionKontaktdatenBearbeiten">{{ $p.t('projektarbeit', 'kontaktdatenBearbeiten') }}</button>
|
||||
<button
|
||||
class="btn btn-outline-secondary float-end"
|
||||
@click="actionKontaktdatenBearbeiten"
|
||||
>
|
||||
{{ $p.t('projektarbeit', 'kontaktdatenBearbeiten') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -605,7 +618,7 @@ export default {
|
||||
</contact>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</bs-modal>
|
||||
|
||||
`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import TemplateFrame from "./template/frame.js";
|
||||
|
||||
export default {
|
||||
name: 'SearchbarResultTeachingUnit',
|
||||
components: {
|
||||
TemplateFrame
|
||||
},
|
||||
emits: [ 'actionexecuted' ],
|
||||
props: {
|
||||
res: Object,
|
||||
actions: Object
|
||||
},
|
||||
template: `
|
||||
<template-frame
|
||||
class="searchbar-result-teachingunit"
|
||||
:res="res"
|
||||
:actions="actions"
|
||||
:title="res.bezeichnung"
|
||||
:image-fallback="res.foto"
|
||||
@actionexecuted="$emit('actionexecuted')"
|
||||
>
|
||||
<div class="searchbar_table">
|
||||
<div class="searchbar_tablerow">
|
||||
<div class="searchbar_tablecell searchbar_label"> {{ res.type === 'lehreinheit' ? $p.t('lehre/lehreinheit') : $p.t('lehre/lehrveranstaltung') }}</div>
|
||||
<div class="searchbar_tablecell searchbar_value">
|
||||
{{ res.id }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="searchbar_tablerow">
|
||||
<div class="searchbar_tablecell searchbar_label">{{ $p.t('lehre/studiengang') }}</div>
|
||||
<div class="searchbar_tablecell searchbar_value">
|
||||
{{ res.studiengang }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="searchbar_tablerow" v-if="res.oe_bezeichnung">
|
||||
<div class="searchbar_tablecell searchbar_label">{{ $p.t('lehre/organisationseinheit') }}</div>
|
||||
<div class="searchbar_tablecell searchbar_value">
|
||||
{{ res.oe_bezeichnung }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="searchbar_tablerow" v-if="res.studiensemester_kurzbz">
|
||||
<div class="searchbar_tablecell searchbar_label">{{ $p.t('lehre/studiensemester') }}</div>
|
||||
<div class="searchbar_tablecell searchbar_value">
|
||||
{{ res.studiensemester_kurzbz }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="searchbar_tablerow">
|
||||
<div class="searchbar_tablecell searchbar_label">{{ $p.t('lehre/ausbildungssemester') }}</div>
|
||||
<div class="searchbar_tablecell searchbar_value">
|
||||
{{ res.semester }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="searchbar_tablerow">
|
||||
<div class="searchbar_tablecell searchbar_label">{{ $p.t('lehre/lehrform') }}</div>
|
||||
<div class="searchbar_tablecell searchbar_value">
|
||||
{{ res.lehrform_kurzbz }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template-frame>`
|
||||
};
|
||||
@@ -8,6 +8,7 @@ import dms from "./result/dms.js";
|
||||
import cms from "./result/cms.js";
|
||||
import mergedStudent from "./result/mergedstudent.js";
|
||||
import mergedPerson from "./result/mergedperson.js";
|
||||
import teachingunit from "./result/teachingunit.js";
|
||||
import { debounce } from "../../helpers/DebounceHelper.js";
|
||||
|
||||
export default {
|
||||
@@ -23,6 +24,7 @@ export default {
|
||||
cms,
|
||||
mergedStudent,
|
||||
mergedPerson,
|
||||
teachingunit,
|
||||
},
|
||||
props: {
|
||||
searchoptions: {
|
||||
|
||||
@@ -1,352 +0,0 @@
|
||||
/**
|
||||
* v-tooltip with a panel that stays in view, also at the screen edge, in a modal and on a phone.
|
||||
*
|
||||
* - The panel is fixed and stays inside the viewport. It opens below the anchor, above it when
|
||||
* it fits only there or that side has more space. It grows away from the nearer viewport side.
|
||||
* - On an anchor wider than the panel, e.g. a long table cell, it opens at the mouse, not at the anchor edge.
|
||||
* - One panel at a time, appended to the body. A modal or a scrolling container cannot clip it.
|
||||
* - A mouse hover or a keyboard focus opens it. Escape, a click outside or a scroll closes it.
|
||||
*
|
||||
* Sticky, for several sentences or a list: a short label is read before the hover pause ends, a long text is not.
|
||||
* - After HOLD_DELAY_MS + HOLD_FILL_MS of hovering the panel sticks, the border fills up in the end.
|
||||
* A click on the anchor makes it stick at once. Touch has no hover: a tap toggles the sticky panel.
|
||||
* - A sticky panel stays until a click outside, Escape, a scroll or an opening bootstrap modal.
|
||||
*
|
||||
* The view needs public/css/components/inViewTooltip.css.
|
||||
*
|
||||
* Usage examples:
|
||||
*
|
||||
* 1) Directive, registered for the app or the component:
|
||||
* app.directive('tooltip', InViewTooltip) or directives: { tooltip: InViewTooltip }
|
||||
* <button v-tooltip="label"> a short label, hover only
|
||||
* <span v-tooltip.sticky="longText"> several sentences or a list
|
||||
* v-tooltip="{ value: text, disabled: bool, sticky: bool }", the modifier .top prefers the place above
|
||||
*
|
||||
* 2) Without a directive, e.g. a tabulator cell. The content is a text, a DOM node or a function:
|
||||
* { event: 'cellMouseEnter', handler: (e, cell) => hoverTooltip(cell.getElement(), () => buildNode(cell), { delay: 300, sticky: true, closeOnClick: true }) },
|
||||
* { event: 'cellMouseLeave', handler: (e, cell) => hideTooltip(cell.getElement()) }
|
||||
*
|
||||
* 3) A ? icon with a help text: components/InViewHelp.js
|
||||
*/
|
||||
|
||||
// hover time until a sticky panel sticks: a pause without motion filters out a pointer that only passes by,
|
||||
// then a short fill shows the pin. A long fill would draw the eye away from the text
|
||||
const HOLD_DELAY_MS = 1000
|
||||
const HOLD_FILL_MS = 400
|
||||
const HIDE_DELAY_MS = 150 // the pointer can cross the gap from the anchor into the panel
|
||||
const WATCH_MS = 250 // an anchor can leave the page without an unmount, e.g. in a hidden keep-alive tab
|
||||
const PANEL_MAX_WIDTH = 512 // px
|
||||
const PANEL_MARGIN = 16 // px between the panel and the viewport edge
|
||||
const PANEL_GAP = 6 // px between the anchor and the panel
|
||||
const PANEL_RADIUS = 5 // px, the inner radius of the panel border. The progress border follows it
|
||||
const PANEL_POINTER_INSET = 16 // px, the horizontal padding of the panel: the text starts below the mouse
|
||||
|
||||
const clamp = (value, min, max) => Math.min(Math.max(value, min), max)
|
||||
|
||||
let active = null // the open panel, only one at a time
|
||||
let pending = null // a delayed show
|
||||
let panelCount = 0
|
||||
|
||||
// the last pointer. Its type tells a tap from a mouse also for the mouse events of tabulator, a tap fires
|
||||
// emulated mouse events too: pointerover fires before them. Its place puts the panel next to the mouse
|
||||
const pointer = { type: 'mouse', x: null, y: null }
|
||||
const trackPointer = event => {
|
||||
pointer.type = event.pointerType
|
||||
pointer.x = event.clientX
|
||||
pointer.y = event.clientY
|
||||
}
|
||||
for (const name of ['pointerover', 'pointermove', 'pointerdown'])
|
||||
document.addEventListener(name, trackPointer, { capture: true, passive: true })
|
||||
|
||||
/**
|
||||
* Opens the panel for an anchor element.
|
||||
*
|
||||
* content: a text, a DOM node or a function that returns one of them.
|
||||
* options: prefer ('bottom' | 'top'), delay (ms), sticky (the hover timer pins the panel), pinned (sticks at once),
|
||||
* closeOnClick (a click on the anchor closes the panel, e.g. a table cell with a click action)
|
||||
*/
|
||||
export function showTooltip(anchor, content, options = {}) {
|
||||
cancelPending()
|
||||
if (!options.delay) return open(anchor, content, options)
|
||||
|
||||
pending = { anchor, timer: setTimeout(() => { pending = null; open(anchor, content, options) }, options.delay) }
|
||||
}
|
||||
|
||||
// for mouse events without a pointer type, e.g. the cell events of tabulator
|
||||
export function hoverTooltip(anchor, content, options) {
|
||||
if (pointer.type === 'mouse') showTooltip(anchor, content, options)
|
||||
}
|
||||
|
||||
// closes the panel of the anchor after HIDE_DELAY_MS, a sticky panel stays
|
||||
export function hideTooltip(anchor) {
|
||||
if (pending?.anchor === anchor) cancelPending()
|
||||
if (active?.anchor !== anchor || active.pinned) return
|
||||
|
||||
clearTimeout(active.hideTimer)
|
||||
active.hideTimer = setTimeout(close, HIDE_DELAY_MS)
|
||||
}
|
||||
|
||||
// closes the open panel, with an anchor only the panel of that anchor
|
||||
export function closeTooltip(anchor) {
|
||||
if (!anchor || pending?.anchor === anchor) cancelPending()
|
||||
if (!anchor || active?.anchor === anchor) close()
|
||||
}
|
||||
|
||||
function isTooltipPinned(anchor) {
|
||||
return active?.anchor === anchor && active.pinned
|
||||
}
|
||||
|
||||
// new content for an open panel, an empty text closes it
|
||||
function updateTooltip(anchor, content) {
|
||||
if (active?.anchor !== anchor) return
|
||||
if (!content) return close()
|
||||
|
||||
render(active.panel, content)
|
||||
place()
|
||||
}
|
||||
|
||||
function open(anchor, content, { prefer = 'bottom', sticky = false, pinned = false, closeOnClick = false }) {
|
||||
if (active?.anchor === anchor) {
|
||||
clearTimeout(active.hideTimer)
|
||||
if (pinned) pin()
|
||||
return
|
||||
}
|
||||
close()
|
||||
|
||||
sticky = sticky || pinned
|
||||
const panel = document.createElement('div')
|
||||
panel.id = 'fhc-in-view-tooltip-' + ++panelCount
|
||||
panel.className = 'fhc-in-view-tooltip'
|
||||
panel.setAttribute('role', 'tooltip')
|
||||
panel.innerHTML = '<div class="fhc-in-view-tooltip-body"></div>'
|
||||
if (sticky) {
|
||||
panel.style.setProperty('--fhc-in-view-tooltip-hold-delay', HOLD_DELAY_MS + 'ms')
|
||||
panel.style.setProperty('--fhc-in-view-tooltip-hold-fill', HOLD_FILL_MS + 'ms')
|
||||
panel.insertAdjacentHTML('beforeend', '<svg class="fhc-in-view-tooltip-progress" aria-hidden="true">'
|
||||
+ '<rect width="100%" height="100%" rx="' + PANEL_RADIUS + '"></rect></svg>')
|
||||
}
|
||||
render(panel, content)
|
||||
document.body.appendChild(panel)
|
||||
|
||||
// the pointer at the opening, while it is over the anchor. A keyboard focus has none
|
||||
const rect = anchor.getBoundingClientRect()
|
||||
const overAnchor = pointer.x !== null && pointer.x >= rect.left && pointer.x <= rect.right
|
||||
&& pointer.y >= rect.top && pointer.y <= rect.bottom
|
||||
|
||||
active = {
|
||||
anchor,
|
||||
panel,
|
||||
prefer,
|
||||
pointerX: overAnchor ? pointer.x : null,
|
||||
sticky,
|
||||
closeOnClick,
|
||||
pinned: false,
|
||||
describedBy: anchor.getAttribute('aria-describedby'),
|
||||
holdTimer: null,
|
||||
hideTimer: null,
|
||||
watchTimer: setInterval(() => {
|
||||
if (!anchor.isConnected || !anchor.getClientRects().length) close()
|
||||
}, WATCH_MS)
|
||||
}
|
||||
place()
|
||||
anchor.setAttribute('aria-describedby', panel.id)
|
||||
|
||||
if (pinned) {
|
||||
pin()
|
||||
} else if (sticky) {
|
||||
panel.classList.add('fhc-in-view-tooltip--holding')
|
||||
active.holdTimer = setTimeout(pin, HOLD_DELAY_MS + HOLD_FILL_MS)
|
||||
}
|
||||
|
||||
panel.addEventListener('pointerenter', onPanelEnter)
|
||||
panel.addEventListener('pointerleave', onPanelLeave)
|
||||
panel.addEventListener('click', onPanelClick)
|
||||
document.addEventListener('pointerdown', onPointerDown, true)
|
||||
document.addEventListener('keydown', onKeyDown, true)
|
||||
// an anchor often opens a modal on click, the sticky panel must not cover it
|
||||
document.addEventListener('show.bs.modal', close)
|
||||
window.addEventListener('scroll', onScroll, true)
|
||||
window.addEventListener('resize', place)
|
||||
}
|
||||
|
||||
function pin() {
|
||||
if (!active || active.pinned || !active.sticky) return
|
||||
|
||||
clearTimeout(active.holdTimer)
|
||||
clearTimeout(active.hideTimer)
|
||||
active.pinned = true
|
||||
active.panel.classList.remove('fhc-in-view-tooltip--holding')
|
||||
active.panel.classList.add('fhc-in-view-tooltip--pinned')
|
||||
}
|
||||
|
||||
function close() {
|
||||
if (!active) return
|
||||
|
||||
const { anchor, panel, describedBy } = active
|
||||
clearTimeout(active.holdTimer)
|
||||
clearTimeout(active.hideTimer)
|
||||
clearInterval(active.watchTimer)
|
||||
active = null
|
||||
|
||||
panel.remove()
|
||||
if (describedBy) anchor.setAttribute('aria-describedby', describedBy)
|
||||
else anchor.removeAttribute('aria-describedby')
|
||||
|
||||
document.removeEventListener('pointerdown', onPointerDown, true)
|
||||
document.removeEventListener('keydown', onKeyDown, true)
|
||||
document.removeEventListener('show.bs.modal', close)
|
||||
window.removeEventListener('scroll', onScroll, true)
|
||||
window.removeEventListener('resize', place)
|
||||
}
|
||||
|
||||
function cancelPending() {
|
||||
if (!pending) return
|
||||
|
||||
clearTimeout(pending.timer)
|
||||
pending = null
|
||||
}
|
||||
|
||||
function render(panel, content) {
|
||||
if (typeof content === 'function') content = content()
|
||||
|
||||
const body = panel.querySelector('.fhc-in-view-tooltip-body')
|
||||
if (content instanceof Node) body.replaceChildren(content)
|
||||
else body.textContent = content
|
||||
}
|
||||
|
||||
function place() {
|
||||
if (!active) return
|
||||
|
||||
const { anchor, panel, prefer } = active
|
||||
const rect = anchor.getBoundingClientRect()
|
||||
const viewWidth = document.documentElement.clientWidth
|
||||
const viewHeight = document.documentElement.clientHeight
|
||||
|
||||
// measure the natural size first
|
||||
panel.style.maxWidth = Math.min(PANEL_MAX_WIDTH, viewWidth - 2 * PANEL_MARGIN) + 'px'
|
||||
panel.style.maxHeight = ''
|
||||
panel.style.left = '0px'
|
||||
panel.style.top = '0px'
|
||||
panel.style.bottom = ''
|
||||
const width = panel.offsetWidth
|
||||
const height = panel.offsetHeight
|
||||
|
||||
const space = {
|
||||
bottom: viewHeight - rect.bottom - PANEL_GAP - PANEL_MARGIN,
|
||||
top: rect.top - PANEL_GAP - PANEL_MARGIN
|
||||
}
|
||||
const other = prefer === 'top' ? 'bottom' : 'top'
|
||||
let side = prefer
|
||||
if (space[prefer] < height) side = space[other] >= height || space[other] > space[prefer] ? other : prefer
|
||||
|
||||
// a panel at least as wide as the anchor starts at the anchor edge, grows away from the nearer viewport side
|
||||
// and spans the whole anchor. On a wider anchor that edge can be far from the mouse: the text starts below
|
||||
// the pointer, the clamp moves the panel to the left only at the right viewport edge
|
||||
const { pointerX } = active
|
||||
let left
|
||||
if (pointerX === null || width >= rect.width) left = rect.left + rect.width / 2 < viewWidth / 2 ? rect.left : rect.right - width
|
||||
else left = pointerX - PANEL_POINTER_INSET
|
||||
panel.style.left = clamp(left, PANEL_MARGIN, viewWidth - PANEL_MARGIN - width) + 'px'
|
||||
panel.style.maxHeight = space[side] + 'px'
|
||||
if (side === 'bottom') {
|
||||
panel.style.top = rect.bottom + PANEL_GAP + 'px'
|
||||
} else {
|
||||
panel.style.top = ''
|
||||
panel.style.bottom = viewHeight - rect.top + PANEL_GAP + 'px'
|
||||
}
|
||||
|
||||
if (!active.sticky) return
|
||||
|
||||
// the dash of the progress border is as long as the rounded rect along the inner edge of the 1 px border.
|
||||
// Rounded up, else a gap stays at its start: clientWidth drops the fraction of the width
|
||||
const box = panel.getBoundingClientRect()
|
||||
const perimeter = 2 * (box.width + box.height - 4) - (8 - 2 * Math.PI) * PANEL_RADIUS
|
||||
panel.style.setProperty('--fhc-in-view-tooltip-perimeter', Math.ceil(perimeter) + 1)
|
||||
}
|
||||
|
||||
function onPanelEnter() {
|
||||
cancelPending()
|
||||
clearTimeout(active.hideTimer)
|
||||
}
|
||||
|
||||
function onPanelLeave(event) {
|
||||
if (event.pointerType === 'mouse') hideTooltip(active.anchor)
|
||||
}
|
||||
|
||||
// a click into the panel closes it, a text selection keeps it
|
||||
function onPanelClick() {
|
||||
if (!String(window.getSelection())) close()
|
||||
}
|
||||
|
||||
function onPointerDown(event) {
|
||||
const { anchor, panel, closeOnClick } = active
|
||||
if (panel.contains(event.target)) return
|
||||
if (anchor.contains(event.target) && !closeOnClick) return
|
||||
|
||||
cancelPending()
|
||||
close()
|
||||
}
|
||||
|
||||
// Escape closes only the panel, not a modal around the anchor
|
||||
function onKeyDown(event) {
|
||||
if (event.key !== 'Escape') return
|
||||
|
||||
event.stopPropagation()
|
||||
close()
|
||||
}
|
||||
|
||||
// the panel is fixed, a scroll moves the anchor away from it. A scroll inside the panel keeps it
|
||||
function onScroll(event) {
|
||||
if (!active.panel.contains(event.target)) close()
|
||||
}
|
||||
|
||||
// reads the binding: "text" or { value, disabled, sticky }, the modifiers .sticky and .top
|
||||
function read(state, binding) {
|
||||
const value = binding.value
|
||||
const isObject = value && typeof value === 'object'
|
||||
state.text = (isObject ? (value.disabled ? '' : value.value) : value) ?? ''
|
||||
state.sticky = !!(binding.modifiers.sticky || isObject && value.sticky)
|
||||
state.prefer = binding.modifiers.top ? 'top' : 'bottom'
|
||||
}
|
||||
|
||||
export default {
|
||||
mounted(el, binding) {
|
||||
const state = el._fhcInViewTooltip = { pointerType: 'mouse' }
|
||||
read(state, binding)
|
||||
const show = options => {
|
||||
if (state.text) showTooltip(el, state.text, { prefer: state.prefer, sticky: state.sticky, ...options })
|
||||
}
|
||||
|
||||
state.listeners = {
|
||||
pointerenter: event => { if (event.pointerType === 'mouse') show() },
|
||||
pointerleave: event => { if (event.pointerType === 'mouse') hideTooltip(el) },
|
||||
pointerdown: event => { state.pointerType = event.pointerType },
|
||||
// a mouse click or a key pins a sticky panel at once, a tap toggles it
|
||||
click: () => {
|
||||
if (!state.sticky) return
|
||||
|
||||
if (state.pointerType !== 'mouse' && isTooltipPinned(el)) closeTooltip(el)
|
||||
else show({ pinned: true })
|
||||
|
||||
state.pointerType = 'mouse' // a key click has no pointerdown
|
||||
},
|
||||
focusin: event => { if (event.target.matches(':focus-visible')) show() },
|
||||
focusout: () => hideTooltip(el)
|
||||
}
|
||||
// the click captures: it pins the panel before the click action of the anchor runs, so a modal
|
||||
// that this action opens closes the panel again. The pointer events must not capture the children
|
||||
for (const [name, listener] of Object.entries(state.listeners)) el.addEventListener(name, listener, name === 'click')
|
||||
},
|
||||
updated(el, binding) {
|
||||
const state = el._fhcInViewTooltip
|
||||
const text = state.text
|
||||
read(state, binding)
|
||||
if (state.text !== text) updateTooltip(el, state.text)
|
||||
},
|
||||
beforeUnmount(el) {
|
||||
const state = el._fhcInViewTooltip
|
||||
closeTooltip(el)
|
||||
for (const [name, listener] of Object.entries(state.listeners)) el.removeEventListener(name, listener, name === 'click')
|
||||
delete el._fhcInViewTooltip
|
||||
}
|
||||
}
|
||||
@@ -47,3 +47,7 @@ function splitWordsInCamelCase(string) {
|
||||
splitWords.push(string.slice(lastUpperCaseLetterIndex));
|
||||
return splitWords;
|
||||
}
|
||||
|
||||
export function isString(str) {
|
||||
return typeof str === 'string' || str instanceof String;
|
||||
}
|
||||
|
||||
+162
-260
@@ -21670,264 +21670,6 @@ array(
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'rauminfo',
|
||||
'phrase' => 'standort',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => "Standort",
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => "Site",
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'rauminfo',
|
||||
'phrase' => 'alleStandorte',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => "Alle Standorte",
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => "All Sites",
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'rauminfo',
|
||||
'phrase' => 'ohneStandort',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => "Ohne Standort",
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => "No Site",
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'rauminfo',
|
||||
'phrase' => 'tooltipRaumsuchev2',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => "Die Raumsuche zeigt freie Räume für einen Zeitraum. Ein Raum ist frei, wenn im Stundenplan keine Lehrveranstaltung steht und niemand den Raum reserviert hat. Beim Öffnen zeigt die Liste die Räume, die ab jetzt für eine Stunde frei sind.
|
||||
|
||||
So finden Sie einen freien Raum:
|
||||
1. Wählen Sie Datum und Uhrzeit.
|
||||
2. Optional: Wählen Sie einen Raumtyp, einen Standort und eine Mindestpersonenkapazität.
|
||||
Die Liste lädt neu, sobald Sie einen Filter ändern.
|
||||
|
||||
In der Liste:
|
||||
• Raum Informationen: Das Symbol öffnet die Infoseite des Raums. Ein Strich bedeutet: Der Raum hat keine Infoseite.
|
||||
• Raum Reservierungen: Das Symbol öffnet den Kalender des Raums. Der Kalender zeigt alle Lehrveranstaltungen und Reservierungen.",
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => "The room search shows free rooms for a time period. A room is free when the timetable shows no course in the room and nobody reserved the room. When you open the page, the list shows the rooms that are free from now for one hour.
|
||||
|
||||
How to find a free room:
|
||||
1. Select the date and the time.
|
||||
2. Optional: select a room type, a site and a minimum person capacity.
|
||||
The list loads again when you change a filter.
|
||||
|
||||
In the list:
|
||||
• Room Information: the icon opens the information page of the room. A dash means that the room has no information page.
|
||||
• Room Reservations: the icon opens the calendar of the room. The calendar shows all courses and reservations.",
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'rauminfo',
|
||||
'phrase' => 'erweiterteFilter',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => "Erweiterte Filter",
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => "Advanced filters",
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'rauminfo',
|
||||
'phrase' => 'nurLehrraeume',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => "Nur Lehrräume",
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => "Only teaching rooms",
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'rauminfo',
|
||||
'phrase' => 'nurMitInfoseite',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => "Nur Räume mit Infoseite",
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => "Only rooms with an information page",
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'rauminfo',
|
||||
'phrase' => 'lehrraum',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => "Lehrraum",
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => "Teaching room",
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'rauminfo',
|
||||
'phrase' => 'stockwerk',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => "Stockwerk",
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => "Floor",
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'rauminfo',
|
||||
'phrase' => 'gebaeudeteil',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => "Gebäudeteil",
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => "Building section",
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'rauminfo',
|
||||
'phrase' => 'flaeche',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => "Fläche in m²",
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => "Area in m²",
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'rauminfo',
|
||||
'phrase' => 'raumtyp',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => "Raumtyp",
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => "Room Type",
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'lehre',
|
||||
@@ -53630,6 +53372,146 @@ and represent the current state of research on the topic. The prescribed citatio
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'lehre',
|
||||
'phrase' => 'lvteilkopieren',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'LV-Teil kopieren',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Copy teaching unit',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'lehre',
|
||||
'phrase' => 'nurlvteil',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Nur LV-Teil',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Teaching unit only',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'lehre',
|
||||
'phrase' => 'nurmitgruppen',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Nur mit Gruppen',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'With groups only',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'lehre',
|
||||
'phrase' => 'nurmitlehrende',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Nur mit Lehrenden',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'With lecturers only',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'lehre',
|
||||
'phrase' => 'zurlvspringen',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Zur LV Springen',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Go to teaching unit',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'lehre',
|
||||
'phrase' => 'lvteillinkkopieren',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Link zu diesem LV-Teil kopieren',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Copy link to this teaching unit',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'lehre',
|
||||
'phrase' => 'alles',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Alles',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Everything',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'lehre',
|
||||
@@ -61595,7 +61477,7 @@ I have been informed that I am under no obligation to consent to the transmissio
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'tabulator',
|
||||
@@ -62869,8 +62751,28 @@ I have been informed that I am under no obligation to consent to the transmissio
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
// ### Infocenter Onboarding END
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'editingMitarbeiterPVonly',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Kontaktdaten von Mitarbeiter*innen dürfen nur in der Personalverwaltung editiert werden.',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Employee contact details may only be edited in the personnel administration system.',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
INSERT INTO lehre.tbl_projektarbeit
|
||||
(projektarbeit_id, projekttyp_kurzbz, titel, lehreinheit_id, student_uid, firma_id, note, punkte, beginn, ende, faktor, freigegeben, gesperrtbis, stundensatz, themenbereich, anmerkung, updateamum, updatevon, insertamum, insertvon, ext_id, gesamtstunden, titel_english, sprache, seitenanzahl, abgabedatum, kontrollschlagwoerter, schlagwoerter, schlagwoerter_en, abstract, abstract_en, "final") VALUES
|
||||
(1, 'Bachelor', 'Auswirkung der Sonneneinstrahlung auf die Gesundheit', 46111, 's46b413', NULL, NULL, NULL, NULL, NULL, NULL, true, NULL, NULL, NULL, NULL, NULL, NULL, '2025-12-12 14:06:10.000', 'anondata', NULL, 3.0000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, true),
|
||||
(2, 'Bachelor', 'Neue Unterrichtsmethoden mit KI', 46111, 's46b414', NULL, NULL, NULL, NULL, NULL, NULL, true, NULL, NULL, NULL, NULL, NULL, NULL, '2025-12-12 14:06:10.000', 'anondata', NULL, 3.0000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, true),
|
||||
(3, 'Bachelor', 'Betrugsbekämpfung bei Anwesenheitskontrolle', 46111, 's46b415', NULL, NULL, NULL, NULL, NULL, NULL, true, NULL, NULL, NULL, NULL, NULL, NULL, '2025-12-12 14:06:10.000', 'anondata', NULL, 3.0000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, true),
|
||||
(4, 'Bachelor', 'Neuronale Netze', 46111, 's46b416', NULL, NULL, NULL, NULL, NULL, NULL, true, NULL, NULL, NULL, NULL, NULL, NULL, '2025-12-12 14:06:10.000', 'anondata', NULL, 3.0000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, true)
|
||||
(projektarbeit_id, projekttyp_kurzbz, titel, lehreinheit_id, student_uid, firma_id, note, punkte, beginn, ende, faktor, freigegeben, gesperrtbis, stundensatz, themenbereich, anmerkung, updateamum, updatevon, insertamum, insertvon, ext_id, gesamtstunden, titel_english, sprache, seitenanzahl, abgabedatum, kontrollschlagwoerter, schlagwoerter, schlagwoerter_en, abstract, abstract_en, "final", lehrveranstaltung_id, studiensemester_kurzbz) VALUES
|
||||
(1, 'Bachelor', 'Auswirkung der Sonneneinstrahlung auf die Gesundheit', 46111, 's46b413', NULL, NULL, NULL, NULL, NULL, NULL, true, NULL, NULL, NULL, NULL, NULL, NULL, '2025-12-12 14:06:10.000', 'anondata', NULL, 3.0000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, true, 4611, NearestWintersemester(1)),
|
||||
(2, 'Bachelor', 'Neue Unterrichtsmethoden mit KI', 46111, 's46b414', NULL, NULL, NULL, NULL, NULL, NULL, true, NULL, NULL, NULL, NULL, NULL, NULL, '2025-12-12 14:06:10.000', 'anondata', NULL, 3.0000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, true, 4611, NearestWintersemester(1)),
|
||||
(3, 'Bachelor', 'Betrugsbekämpfung bei Anwesenheitskontrolle', 46111, 's46b415', NULL, NULL, NULL, NULL, NULL, NULL, true, NULL, NULL, NULL, NULL, NULL, NULL, '2025-12-12 14:06:10.000', 'anondata', NULL, 3.0000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, true, 4611, NearestWintersemester(1)),
|
||||
(4, 'Bachelor', 'Neuronale Netze', 46111, 's46b416', NULL, NULL, NULL, NULL, NULL, NULL, true, NULL, NULL, NULL, NULL, NULL, NULL, '2025-12-12 14:06:10.000', 'anondata', NULL, 3.0000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, true, 4611, NearestWintersemester(1))
|
||||
;
|
||||
|
||||
INSERT INTO lehre.tbl_projektbetreuer
|
||||
|
||||
Reference in New Issue
Block a user