From ac44b36b59eef91875c44782add7df81875ead0b Mon Sep 17 00:00:00 2001 From: Johann Hoffmann Date: Tue, 7 Apr 2026 16:07:10 +0200 Subject: [PATCH 01/12] =?UTF-8?q?cis4=20myLv=20employee=20functionality=20?= =?UTF-8?q?implemented;=20wrote=20new=20query=20fetching=20studiensemester?= =?UTF-8?q?=20an=20employee=20in=20which=20he=20had=20assigned=20lehrauftr?= =?UTF-8?q?=C3=A4ge;=20afterwards=20fetch=20all=20lva=20by=20employee=20ui?= =?UTF-8?q?d=20and=20sem=5Fkurzbz=20so=20they=20fit=20in=20the=20existing?= =?UTF-8?q?=20component=20structure;=20MyLv/Lvs/SS2026=20etc=20now=20also?= =?UTF-8?q?=20returns=20a=20'student'=20|=20'employee'=20string=20based=20?= =?UTF-8?q?on=20path=20taken=20inside=20the=20controller=20to=20aid=20vue?= =?UTF-8?q?=20components=20in=20rendering=20useful=20information=20and=20a?= =?UTF-8?q?voiding=20senseless=20fetch=20requests=20->=20e.g.=20employees?= =?UTF-8?q?=20cant=20have=20grades=20on=20lva=20they=20are=20teaching;=20a?= =?UTF-8?q?dded=20component=20names=20for=20sanity=20when=20using=20vuedev?= =?UTF-8?q?tools;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/components/Cis/Mylv.php | 45 +++++++++++++++---- .../education/Lehrveranstaltung_model.php | 31 +++++++++++++ .../organisation/Studiensemester_model.php | 24 ++++++++++ public/js/components/Cis/Mylv/LvMenu.js | 1 + public/js/components/Cis/Mylv/LvUebersicht.js | 2 +- public/js/components/Cis/Mylv/Semester.js | 1 + .../Cis/Mylv/Semester/Studiengang.js | 1 + .../Cis/Mylv/Semester/Studiengang/Lv.js | 21 +++++---- public/js/components/Cis/Mylv/Student.js | 10 ++++- 9 files changed, 118 insertions(+), 18 deletions(-) diff --git a/application/controllers/components/Cis/Mylv.php b/application/controllers/components/Cis/Mylv.php index 1fdb7e2a1..3ce6106b0 100644 --- a/application/controllers/components/Cis/Mylv.php +++ b/application/controllers/components/Cis/Mylv.php @@ -44,13 +44,28 @@ class Mylv extends Auth_Controller public function Studiensemester() { $this->load->model('organisation/Studiensemester_model', 'StudiensemesterModel'); + $this->load->model('crm/Student_model', 'StudentModel'); + $this->load->model('ressource/Mitarbeiter_model', 'MitarbeiterModel'); - $result = $this->StudiensemesterModel->getWhereStudentHasLvs(getAuthUID()); + $isMitarbeiter = getData($this->MitarbeiterModel->isMitarbeiter(getAuthUID())) ?? false; + $isStudent = getData($this->StudentModel->isStudent(getAuthUID())) ?? false; + if($isMitarbeiter) { + $result = $this->StudiensemesterModel->getWhereMitarbeiterHasLvs(getAuthUID()); - if (isError($result)) - return $this->outputJsonError(getError($result)); + if (isError($result)) + return $this->outputJsonError(getError($result)); - $this->outputJsonSuccess(getData($result)); + $this->outputJsonSuccess(getData($result)); + } else if($isStudent) { // $isStudent + $result = $this->StudiensemesterModel->getWhereStudentHasLvs(getAuthUID()); + + if (isError($result)) + return $this->outputJsonError(getError($result)); + + $this->outputJsonSuccess(getData($result)); + } else { + $this->outputJsonError('neither student or mitarbeiter'); + } } /** @@ -58,13 +73,27 @@ class Mylv extends Auth_Controller public function Lvs($studiensemester_kurzbz) { $this->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel'); + $this->load->model('crm/Student_model', 'StudentModel'); + $this->load->model('ressource/Mitarbeiter_model', 'MitarbeiterModel'); - $result = $this->LehrveranstaltungModel->getLvsByStudentWithGrades(getAuthUID(), $studiensemester_kurzbz, getUserLanguage()); + $isMitarbeiter = getData($this->MitarbeiterModel->isMitarbeiter(getAuthUID())) ?? false; + if($isMitarbeiter) { + $result = $this->LehrveranstaltungModel->getLvsByMitarbeiterInSemester(getAuthUID(), $studiensemester_kurzbz); - if (isError($result)) - return $this->outputJsonError(getError($result)); + if (isError($result)) + return $this->outputJsonError(getError($result)); - $this->outputJsonSuccess(getData($result)); + $this->outputJsonSuccess([getData($result), 'employee']); + } else if(getData($this->StudentModel->isStudent(getAuthUID())) ?? false) { // $isStudent + $result = $this->LehrveranstaltungModel->getLvsByStudentWithGrades(getAuthUID(), $studiensemester_kurzbz, getUserLanguage()); + + if (isError($result)) + return $this->outputJsonError(getError($result)); + + $this->outputJsonSuccess([getData($result), 'student']); + } else { + $this->outputJsonError('neither student or mitarbeiter'); + } } /** diff --git a/application/models/education/Lehrveranstaltung_model.php b/application/models/education/Lehrveranstaltung_model.php index ccac33bc7..76c7b85fc 100644 --- a/application/models/education/Lehrveranstaltung_model.php +++ b/application/models/education/Lehrveranstaltung_model.php @@ -1343,4 +1343,35 @@ class Lehrveranstaltung_model extends DB_Model return $this->execQuery($qry, $params); } + + // used for cis4 mylv mitarbeiter + public function getLvsByMitarbeiterInSemester($mitarbeiter_uid, $sem_kurzbz) { + $qry = "SELECT + public.tbl_studiengang.studiengang_kz, + lehre.tbl_lehrveranstaltung.semester, + public.tbl_studiengang.bezeichnung as sg_bezeichnung, + public.tbl_studiengang.english as sg_bezeichnung_eng, + UPPER(tbl_studiengang.typ::varchar(1) || tbl_studiengang.kurzbz) as studiengang_kuerzel, + lehre.tbl_lehrveranstaltung.lehrveranstaltung_id, + lehre.tbl_lehrveranstaltung.bezeichnung, + lehre.tbl_lehrveranstaltung.bezeichnung_english as bezeichnung_eng, + lehre.tbl_lehrveranstaltung.farbe, + lehre.tbl_lehrveranstaltung.lvinfo, + lehre.tbl_lehrveranstaltung.benotung, + lehre.tbl_lehrveranstaltung.orgform_kurzbz, + lehre.tbl_lehrveranstaltung.sprache, + lehre.tbl_lehrveranstaltung.ects, + lehre.tbl_lehrveranstaltung.incoming + FROM + lehre.tbl_lehreinheit JOIN lehre.tbl_lehreinheitmitarbeiter USING(lehreinheit_id) + JOIN lehre.tbl_lehrveranstaltung USING(lehrveranstaltung_id) + JOIN public.tbl_studiengang USING(studiengang_kz) + JOIN lehre.tbl_lehrveranstaltung as lehrfach ON(tbl_lehreinheit.lehrfach_id=lehrfach.lehrveranstaltung_id) + WHERE + tbl_lehreinheit.studiensemester_kurzbz = ? + AND mitarbeiter_uid = ? + ORDER BY studiengang_kuerzel, lehre.tbl_lehrveranstaltung.semester, lehre.tbl_lehrveranstaltung.bezeichnung"; + + return $this->execReadOnlyQuery($qry, [$sem_kurzbz, $mitarbeiter_uid]); + } } diff --git a/application/models/organisation/Studiensemester_model.php b/application/models/organisation/Studiensemester_model.php index 5fa6ffb14..bed138b8a 100644 --- a/application/models/organisation/Studiensemester_model.php +++ b/application/models/organisation/Studiensemester_model.php @@ -242,6 +242,30 @@ class Studiensemester_model extends DB_Model return $this->loadWhere(['uid' => $student_uid, 'v.lehre' => true]); } + public function getWhereMitarbeiterHasLvs($uid) { + // first order by year with last 2 letter from right, + // then order by WS/SS inside the years + // query it asc so the ordering magic in cis4 turns it around again + $qry = "WITH unique_semesters AS ( + SELECT DISTINCT ON (studiensemester_kurzbz) + studiensemester_kurzbz, + start, + ende, + bezeichnung, + studienjahr_kurzbz + FROM lehre.tbl_lehreinheit + JOIN lehre.tbl_lehreinheitmitarbeiter USING(lehreinheit_id) + JOIN public.tbl_studiensemester USING(studiensemester_kurzbz) + WHERE mitarbeiter_uid = ? + ) + SELECT * FROM unique_semesters + ORDER BY + RIGHT(studiensemester_kurzbz, 2) ASC, + LEFT(studiensemester_kurzbz, 2) ASC;"; + + return $this->execReadOnlyQuery($qry, [$uid]); + } + public function getAktAndFutureSemester() { $query = 'SELECT studiensemester_kurzbz diff --git a/public/js/components/Cis/Mylv/LvMenu.js b/public/js/components/Cis/Mylv/LvMenu.js index ef820ebb0..101f0be0f 100644 --- a/public/js/components/Cis/Mylv/LvMenu.js +++ b/public/js/components/Cis/Mylv/LvMenu.js @@ -1,5 +1,6 @@ export default { + name: 'LvMenu', props:{ menu:{ type:Array, diff --git a/public/js/components/Cis/Mylv/LvUebersicht.js b/public/js/components/Cis/Mylv/LvUebersicht.js index 1ec93590f..3303f4b52 100644 --- a/public/js/components/Cis/Mylv/LvUebersicht.js +++ b/public/js/components/Cis/Mylv/LvUebersicht.js @@ -4,7 +4,7 @@ import LvMenu from "./LvMenu.js"; import ApiAddons from '../../../api/factory/addons.js'; export default { - + name: 'LvUebersicht', props:{ event:{ type:Object, diff --git a/public/js/components/Cis/Mylv/Semester.js b/public/js/components/Cis/Mylv/Semester.js index 994a7c9ff..5f9e1153d 100644 --- a/public/js/components/Cis/Mylv/Semester.js +++ b/public/js/components/Cis/Mylv/Semester.js @@ -1,6 +1,7 @@ import MylvSemesterStudiengang from "./Semester/Studiengang.js"; export default { + name: 'Semester', components: { MylvSemesterStudiengang }, diff --git a/public/js/components/Cis/Mylv/Semester/Studiengang.js b/public/js/components/Cis/Mylv/Semester/Studiengang.js index 15d210796..ba6b4f0ca 100644 --- a/public/js/components/Cis/Mylv/Semester/Studiengang.js +++ b/public/js/components/Cis/Mylv/Semester/Studiengang.js @@ -2,6 +2,7 @@ import MylvSemesterStudiengangLv from "./Studiengang/Lv.js"; import Phrasen from "../../../../mixins/Phrasen.js"; export default { + name: 'Studiengang', components: { MylvSemesterStudiengangLv }, diff --git a/public/js/components/Cis/Mylv/Semester/Studiengang/Lv.js b/public/js/components/Cis/Mylv/Semester/Studiengang/Lv.js index 3fd5e6900..1cc128f84 100644 --- a/public/js/components/Cis/Mylv/Semester/Studiengang/Lv.js +++ b/public/js/components/Cis/Mylv/Semester/Studiengang/Lv.js @@ -9,13 +9,14 @@ import ApiAddons from '../../../../../api/factory/addons.js'; // TODO(chris): L10n export default { + name: 'Lv', components:{ LvUebersicht, }, mixins: [ Phrasen ], - inject: ['studien_semester'], + inject: ['studien_semester', 'type'], props: { lehrveranstaltung_id: Number, bezeichnung: String, @@ -152,12 +153,16 @@ export default { } }, created() { - this.$api - .call(ApiLehre.getStudentPruefungen(this.lehrveranstaltung_id)) - .then(res => res.data) - .then(pruefungen => { - this.pruefungenData = pruefungen; - }); + // TODO: check if this isnt a race condition in disguise + if(this.type == 'student') { + this.$api + .call(ApiLehre.getStudentPruefungen(this.lehrveranstaltung_id)) + .then(res => res.data) + .then(pruefungen => { + this.pruefungenData = pruefungen; + }); + } + }, mounted() { this.fetchMenu(this.lehrveranstaltung_id, this.studien_semester); @@ -204,7 +209,7 @@ export default { - ` }; \ No newline at end of file From ccd8d5f8711d5e28966b275a98de6c456228eac6 Mon Sep 17 00:00:00 2001 From: Johann Hoffmann Date: Mon, 20 Apr 2026 10:28:49 +0200 Subject: [PATCH 05/12] WIP alternative table representation of Cis4 MyLv page for teachers assigned to a lot of different degree programs -> cards layout would result in a lot of fluff & scroll; finalized getMultipleLvMenu() functionality in LvMenu.php controller to avoid http request overhead; deleted phrasen mixin from old components since this is a plugin bound to the apps globalConfigince some time already; Allow to switch layout similar to calendar modes -> need to further adapt old component structure to resuse lvmenu data --- .../controllers/api/frontend/v1/LvMenu.php | 81 +++++++------- .../education/Lehrveranstaltung_model.php | 53 ++++----- public/js/api/factory/addons.js | 15 +++ public/js/components/Cis/Mylv/MyLv.js | 51 +++++++-- public/js/components/Cis/Mylv/Semester.js | 9 +- .../Cis/Mylv/Semester/Studiengang.js | 3 +- .../Cis/Mylv/Semester/Studiengang/Lv.js | 6 -- public/js/components/Cis/Mylv/Table.js | 101 ++++++++++++++++++ .../js/components/Cis/Raumsuche/Raumsuche.js | 4 +- 9 files changed, 233 insertions(+), 90 deletions(-) create mode 100644 public/js/components/Cis/Mylv/Table.js diff --git a/application/controllers/api/frontend/v1/LvMenu.php b/application/controllers/api/frontend/v1/LvMenu.php index 45936d9f5..068eaf580 100644 --- a/application/controllers/api/frontend/v1/LvMenu.php +++ b/application/controllers/api/frontend/v1/LvMenu.php @@ -36,7 +36,8 @@ class LvMenu extends FHCAPI_Controller public function __construct() { parent::__construct([ - 'getLvMenu' => self::PERM_LOGGED + 'getLvMenu' => self::PERM_LOGGED, + 'getMultipleLvMenu' => self::PERM_LOGGED ]); $this->load->model("ressource/Mitarbeiter_model"); @@ -62,23 +63,18 @@ class LvMenu extends FHCAPI_Controller /** * alternative function to get multiple lvMenus with a single http request */ - public function getMultipleLvMenu($lvMenuOptionList){ + public function getMultipleLvMenu(){ + $lvMenuOptionList = $this->input->post('lvMenuOptionList', true); $result =[]; foreach($lvMenuOptionList as $lvMenuOptions){ $lvMenu = $this->getLvMenu($lvMenuOptions['lvid'],$lvMenuOptions['studiensemester_kurzbz']); - if(isError($lvMenu)){ - // TODO: some lvMenu threw an error, handle error here - } + $result[$lvMenuOptions['lvid']]=$lvMenu; } $this->terminateWithSuccess($result); } - - /** - * - */ - public function getLvMenu($lvid, $studiensemester_kurzbz) - { + + private function getLvMenuInternal($lvid, $studiensemester_kurzbz) { // return early if parameters are missing if(!isset($lvid) || !isset($studiensemester_kurzbz)) @@ -89,14 +85,14 @@ class LvMenu extends FHCAPI_Controller // get the user if (!$user=getAuthUID()) - $this->terminateWithError($this->p->t('global', 'nichtAngemeldet')); + $this->terminateWithError($this->p->t('global', 'nichtAngemeldet')); // check if is_lector $is_lector = false; $mares = $this->Mitarbeiter_model->isMitarbeiter($user); if(hasData($mares)) { - $is_lector = getData($mares); + $is_lector = getData($mares); } // definition of user_is_allowed_to_upload @@ -105,7 +101,7 @@ class LvMenu extends FHCAPI_Controller // load lehrveranstaltung $lvres = $this->Lehrveranstaltung_model->load($lvid); - if(!hasData($lvres)) + if(!hasData($lvres)) { $this->terminateWithError('LV ' . $lvid . ' not found.'); } @@ -124,7 +120,7 @@ class LvMenu extends FHCAPI_Controller $stgres = $this->Studiengang_model->load(strval($studiengang_kz)); if(!hasData($stgres)) { - $this->terminateWithError('Stg ' . $lv->studiengang_kz . ' not found.'); + $this->terminateWithError('Stg ' . $lv->studiengang_kz . ' not found.'); } $stg = (getData($stgres))[0]; $kurzbz = strtoupper($stg->typ . $stg->kurzbz); @@ -139,7 +135,7 @@ class LvMenu extends FHCAPI_Controller $angemeldet = false; $lesres = $this->Lehreinheit_model->getLehreinheitenForStudentAndStudienSemester( - $lvid, $user, $angezeigtes_stsem + $lvid, $user, $angezeigtes_stsem ); if(hasData($lesres) && count(getData($lesres)) > 0) @@ -148,7 +144,7 @@ class LvMenu extends FHCAPI_Controller // lehrfach $lehrfach_id=''; - + if(defined('CIS_LEHRVERANSTALTUNG_LEHRFACH_ANZEIGEN') && CIS_LEHRVERANSTALTUNG_LEHRFACH_ANZEIGEN) { // Wenn der eingeloggte User zu einer der Lehreinheiten zugeteilt ist @@ -211,8 +207,8 @@ class LvMenu extends FHCAPI_Controller foreach($fbs as $row) { $lehrfach_oe_kurzbz_arr[] = $row->oe_kurzbz; - if($this->PermissionLib->isBerechtigt('lehre', null, $row->oe_kurzbz) - || $this->PermissionLib->isBerechtigt('assistenz', null, $stg->oe_kurzbz)) + if($this->PermissionLib->isBerechtigt('lehre', null, $row->oe_kurzbz) + || $this->PermissionLib->isBerechtigt('assistenz', null, $stg->oe_kurzbz)) { $user_is_allowed_to_upload=true; } @@ -224,21 +220,21 @@ class LvMenu extends FHCAPI_Controller $menu = array(); $this->fhc_menu_lvinfo($menu, $lvid, $studiengang_kz, $lektor_der_lv, $is_lector, $lehrfach_oe_kurzbz_arr); - + $this->fhc_menu_feedback($menu, $angemeldet, $lvid); - + $this->fhc_menu_gesamtnote($menu, $angemeldet, $lvid, $lv, $is_lector, $angezeigtes_stsem); - + $this->fhc_menu_emailStudierende($menu, $user, $angemeldet, $lvid, $angezeigtes_stsem); - + $this->fhc_menu_abmeldung($menu, $user, $is_lector, $lvid, $angezeigtes_stsem); - + $this->fhc_menu_lehretools($menu, $lvid, $angezeigtes_stsem, $sprache); - + $this->fhc_menu_anrechnungStudent($menu, $lvid, $angezeigtes_stsem); - + $this->fhc_menu_anrechnungLector($menu, $angezeigtes_stsem); - + // Addons Menu Logic // ########################################################################################## @@ -272,18 +268,18 @@ class LvMenu extends FHCAPI_Controller 'permissionLib' => &$this->PermissionLib, 'phrasesLib' => &$this->PhrasesLib ]; - - Events::trigger('lvMenuBuild', - // passing $menu per reference - function & () use (&$menu) { - return $menu; - }, - $params + + Events::trigger('lvMenuBuild', + // passing $menu per reference + function & () use (&$menu) { + return $menu; + }, + $params ); // Menu sortieren // ########################################################################################## - + foreach ($menu as $key => $row){ // removes menu points that are not needed in the c4 lvUebersicht @@ -291,7 +287,7 @@ class LvMenu extends FHCAPI_Controller unset($menu[$key]); continue; } - + // fills pos array to sort the menu $pos[$key] = $row['position']; @@ -299,11 +295,18 @@ class LvMenu extends FHCAPI_Controller array_multisort($pos, SORT_ASC, SORT_NUMERIC, $menu); - // HTTP response - // ########################################################################################## + + return $menu; + } + + /** + * + */ + public function getLvMenu($lvid, $studiensemester_kurzbz) + { + $menu = $this->getLvMenuInternal($lvid, $studiensemester_kurzbz); $this->terminateWithSuccess($menu); - } private function fhc_menu_lvinfo(&$menu, $lvid, $studiengang_kz, $lektor_der_lv, $is_lector, $lehrfach_oe_kurzbz_arr){ diff --git a/application/models/education/Lehrveranstaltung_model.php b/application/models/education/Lehrveranstaltung_model.php index 5306ae50a..1c32096a9 100644 --- a/application/models/education/Lehrveranstaltung_model.php +++ b/application/models/education/Lehrveranstaltung_model.php @@ -1346,32 +1346,33 @@ class Lehrveranstaltung_model extends DB_Model // used for cis4 mylv mitarbeiter public function getLvsByMitarbeiterInSemester($mitarbeiter_uid, $sem_kurzbz) { - $qry = "SELECT - public.tbl_studiengang.studiengang_kz, - lehre.tbl_lehrveranstaltung.semester, - public.tbl_studiengang.bezeichnung as sg_bezeichnung, - public.tbl_studiengang.english as sg_bezeichnung_eng, - UPPER(tbl_studiengang.typ::varchar(1) || tbl_studiengang.kurzbz) as studiengang_kuerzel, - lehre.tbl_lehrveranstaltung.lehrveranstaltung_id, - lehre.tbl_lehrveranstaltung.bezeichnung, - lehre.tbl_lehrveranstaltung.bezeichnung_english as bezeichnung_eng, - lehre.tbl_lehreinheitmitarbeiter.semesterstunden as semesterstunden, - lehre.tbl_lehrveranstaltung.farbe, - lehre.tbl_lehrveranstaltung.lvinfo, - lehre.tbl_lehrveranstaltung.benotung, - lehre.tbl_lehrveranstaltung.orgform_kurzbz, - lehre.tbl_lehrveranstaltung.sprache, - lehre.tbl_lehrveranstaltung.ects, - lehre.tbl_lehrveranstaltung.incoming - FROM - lehre.tbl_lehreinheit JOIN lehre.tbl_lehreinheitmitarbeiter USING(lehreinheit_id) - JOIN lehre.tbl_lehrveranstaltung USING(lehrveranstaltung_id) - JOIN public.tbl_studiengang USING(studiengang_kz) - JOIN lehre.tbl_lehrveranstaltung as lehrfach ON(tbl_lehreinheit.lehrfach_id=lehrfach.lehrveranstaltung_id) - WHERE - tbl_lehreinheit.studiensemester_kurzbz = ? - AND mitarbeiter_uid = ? - ORDER BY studiengang_kuerzel, lehre.tbl_lehrveranstaltung.semester, lehre.tbl_lehrveranstaltung.bezeichnung"; + $qry = "SELECT * FROM ( + SELECT DISTINCT ON (lehre.tbl_lehrveranstaltung.lehrveranstaltung_id) + public.tbl_studiengang.studiengang_kz, + lehre.tbl_lehrveranstaltung.semester, + public.tbl_studiengang.bezeichnung as sg_bezeichnung, + public.tbl_studiengang.english as sg_bezeichnung_eng, + UPPER(tbl_studiengang.typ::varchar(1) || tbl_studiengang.kurzbz) as studiengang_kuerzel, + lehre.tbl_lehrveranstaltung.lehrveranstaltung_id, + lehre.tbl_lehrveranstaltung.bezeichnung, + lehre.tbl_lehrveranstaltung.bezeichnung_english as bezeichnung_eng, + TRUNC(lehre.tbl_lehreinheitmitarbeiter.semesterstunden) as semesterstunden, + lehre.tbl_lehrveranstaltung.farbe, + lehre.tbl_lehrveranstaltung.lvinfo, + lehre.tbl_lehrveranstaltung.benotung, + lehre.tbl_lehrveranstaltung.orgform_kurzbz, + lehre.tbl_lehrveranstaltung.sprache, + lehre.tbl_lehrveranstaltung.ects, + lehre.tbl_lehrveranstaltung.incoming + FROM + lehre.tbl_lehreinheit JOIN lehre.tbl_lehreinheitmitarbeiter USING(lehreinheit_id) + JOIN lehre.tbl_lehrveranstaltung USING(lehrveranstaltung_id) + JOIN public.tbl_studiengang USING(studiengang_kz) + JOIN lehre.tbl_lehrveranstaltung as lehrfach ON(tbl_lehreinheit.lehrfach_id=lehrfach.lehrveranstaltung_id) + WHERE + tbl_lehreinheit.studiensemester_kurzbz = ? + AND mitarbeiter_uid = ?) as distincted_by_lva_id + ORDER BY studiengang_kuerzel, semester, bezeichnung"; return $this->execReadOnlyQuery($qry, [$sem_kurzbz, $mitarbeiter_uid]); } diff --git a/public/js/api/factory/addons.js b/public/js/api/factory/addons.js index 23fd19e39..90030d21f 100644 --- a/public/js/api/factory/addons.js +++ b/public/js/api/factory/addons.js @@ -21,5 +21,20 @@ export default { method: 'get', url: `/api/frontend/v1/LvMenu/getLvMenu/${lvid}/${studiensemester_kurzbz}` }; + }, + getMultipleLvMenu(lvas, studiensemester_kurzbz) { + // format params for backend bulk function + const lvMenuOptionList = lvas.map(lva => { + return { + lvid: lva.lehrveranstaltung_id, + studiensemester_kurzbz + } + }) + + return { + method: 'post', + url: `/api/frontend/v1/LvMenu/getMultipleLvMenu`, + params: { lvMenuOptionList } + }; } }; \ No newline at end of file diff --git a/public/js/components/Cis/Mylv/MyLv.js b/public/js/components/Cis/Mylv/MyLv.js index 4bbc161bd..b4b5f1a6b 100644 --- a/public/js/components/Cis/Mylv/MyLv.js +++ b/public/js/components/Cis/Mylv/MyLv.js @@ -1,5 +1,6 @@ -import MylvSemester from "./Semester.js"; -import Phrasen from "../../../mixins/Phrasen.js"; +import MylvSemesterCards from "./Semester.js"; +import MylvTable from "./Table.js"; +import ApiAddons from "../../../api/factory/addons.js" // TODO(chris): phrase: global/studiensemester_auswaehlen // TODO(chris): phrase: next & prev +aria-label @@ -7,22 +8,23 @@ import Phrasen from "../../../mixins/Phrasen.js"; export default { name: 'MyLv', components: { - MylvSemester + MylvSemesterCards, + MylvTable }, - mixins: [ - Phrasen - ], data: () => { return { firstLoad: true, studiensemester: null, lvs: {}, - currentSemester: null + currentSemester: null, + lvMenues: null, + mode: 'table' // TODO: load from local storage }; }, provide() { return { - type: Vue.computed(() => this.type) + type: Vue.computed(() => this.type), + lvMenues: Vue.computed(() => this.lvMenues) } }, inject: ['isStudent', 'isMitarbeiter'], @@ -46,7 +48,12 @@ export default { axios.get(FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/components/Cis/Mylv/Lvs/' + this.currentSemester).then(res => { this.lvs[this.currentSemester].lvs = res.data.retval || []; this.firstLoad = false; - }); + + this.$api.call(ApiAddons.getMultipleLvMenu(this.lvs[this.currentSemester].lvs, this.currentSemester)).then(res => { + this.lvMenues = res.data + }) + + }) } return this.lvs[this.currentSemester]; }, @@ -79,6 +86,9 @@ export default { } }, methods: { + clickMode(evt, mode) { + this.mode = mode + }, prevSem() { this.$refs.studiensemester.selectedIndex--; this.$refs.studiensemester.dispatchEvent(new Event('change', { bubbles: true })); @@ -129,11 +139,32 @@ export default { +
+
+ + +
+
- + +
diff --git a/public/js/components/Cis/Mylv/Semester.js b/public/js/components/Cis/Mylv/Semester.js index dbfddb987..e8f263192 100644 --- a/public/js/components/Cis/Mylv/Semester.js +++ b/public/js/components/Cis/Mylv/Semester.js @@ -12,11 +12,7 @@ export default { }, props: { semester: [String, Number], - lvs: Array, - mode: { - type: String, - default: 'Cards' - } + lvs: Array }, computed: { ready() { return this.lvs !== null; }, @@ -41,7 +37,8 @@ export default { return this.lvs.filter(lv => lv.studiengang_kz == studiengang.studiengang_kz && lv.semester == studiengang.semester); } }, - template: `
+ template: ` +
diff --git a/public/js/components/Cis/Mylv/Semester/Studiengang.js b/public/js/components/Cis/Mylv/Semester/Studiengang.js index ba6b4f0ca..1b0df068d 100644 --- a/public/js/components/Cis/Mylv/Semester/Studiengang.js +++ b/public/js/components/Cis/Mylv/Semester/Studiengang.js @@ -32,7 +32,8 @@ export default { return lv.benotung ? lv.znote || lv.lvnote || null : null; } }, - template: `
+ template: ` +

{{$p.user_language.value === 'English' ? sg_bezeichnung_eng : bezeichnung}} - {{kuerzel}} {{semester}}.{{$p.t('lehre/semester')}} diff --git a/public/js/components/Cis/Mylv/Semester/Studiengang/Lv.js b/public/js/components/Cis/Mylv/Semester/Studiengang/Lv.js index 81d5921c1..c1afd65d0 100644 --- a/public/js/components/Cis/Mylv/Semester/Studiengang/Lv.js +++ b/public/js/components/Cis/Mylv/Semester/Studiengang/Lv.js @@ -1,6 +1,4 @@ import LvPruefungen from "./Lv/Pruefungen.js"; -import LvInfo from "./Lv/Info.js"; -import Phrasen from "../../../../../mixins/Phrasen.js"; import ApiLehre from '../../../../../api/factory/lehre.js'; import ApiAddons from '../../../../../api/factory/addons.js'; @@ -9,9 +7,6 @@ import ApiAddons from '../../../../../api/factory/addons.js'; export default { name: 'Lv', - mixins: [ - Phrasen - ], inject: ['studien_semester', 'type'], props: { lehrveranstaltung_id: [Number, String], @@ -115,7 +110,6 @@ export default { } }, created() { - // TODO: check if this isnt a race condition in disguise if(this.type == 'student') { this.$api .call(ApiLehre.getStudentPruefungen(this.lehrveranstaltung_id)) diff --git a/public/js/components/Cis/Mylv/Table.js b/public/js/components/Cis/Mylv/Table.js new file mode 100644 index 000000000..f1b624b01 --- /dev/null +++ b/public/js/components/Cis/Mylv/Table.js @@ -0,0 +1,101 @@ +import {CoreFilterCmpt} from "../../../components/filter/Filter.js"; + +export default { + name: 'MylvTable', + components: { + CoreFilterCmpt + }, + props: { + semester: [String], + lvs: Array, + }, + data() { + return { + phrasenPromise: null, + phrasenResolved: false, + tabulatorUuid: Vue.ref(0), + tableBuiltResolve: null, + tableBuiltPromise: null, + mylvTableOptions: { + height: Vue.ref(400), + index: 'lehrveranstaltung_id', + layout: 'fitDataStretch', + placeholder: this.$p.t('global/noDataAvailable'), + columns: [ + {title: Vue.computed(() => this.$p.t('lehre/studiengang')), field: 'sg_bezeichnung', widthGrow: 1}, + {title: Vue.computed(() => this.$p.t('global/bezeichnung')), field: 'bezeichnung', widthGrow: 2}, + {title: Vue.computed(() => this.$p.t('lehre/orgform')), field: 'orgform_kurzbz', widthGrow: 1}, + {title: Vue.computed(() => this.$p.t('admission/stg_kurz')), field: 'studiengang_kuerzel', widthGrow: 1}, + {title: Vue.computed(() => this.$p.t('global/actions')), + field: 'menu', formatter: this.actionFormatter, widthGrow: 1} + ], + persistence: false, + persistenceID: "mylv_2026_04_17" + }, + mylvTableEventHandlers: [ + { + event: "tableBuilt", + handler: async () => { + this.tableBuiltResolve() + } + } + ] + } + }, + computed: { + ready() { return this.lvs !== null; }, + + }, + methods: { + handleUuidDefined(uuid) { + this.tabulatorUuid = uuid + }, + tableResolve(resolve) { + this.tableBuiltResolve = resolve + }, + actionFormatter() { + return '' + }, + async setupData() { + this.$refs.mylvTable.tabulator.setData(this.lvs); + }, + async setupMounted() { + + this.tableBuiltPromise = new Promise(this.tableResolve) + await this.tableBuiltPromise + + this.setupData() + + const tableID = this.tabulatorUuid ? ('-' + this.tabulatorUuid) : '' + const tableDataSet = document.getElementById('filterTableDataset' + tableID); + if(!tableDataSet) return + const rect = tableDataSet.getBoundingClientRect(); + + const h = window.visualViewport.height - rect.top - 100 + if(this.$refs.mylvTable) { + this.$refs.mylvTable.$refs.table.style.setProperty('height', h+'px') + } + + } + }, + created() { + this.phrasenPromise = this.$p.loadCategory(['global']) + this.phrasenPromise.then(()=> {this.phrasenResolved = true}) + }, + mounted() { + this.setupMounted() + }, + template: ` +
+ +
` +}; \ No newline at end of file diff --git a/public/js/components/Cis/Raumsuche/Raumsuche.js b/public/js/components/Cis/Raumsuche/Raumsuche.js index 365da4911..58c3bbeb6 100644 --- a/public/js/components/Cis/Raumsuche/Raumsuche.js +++ b/public/js/components/Cis/Raumsuche/Raumsuche.js @@ -70,8 +70,8 @@ export const Raumsuche = { handler: async () => { this.tableBuiltResolve() } - } - ]}; + }] + }; }, methods: { tableResolve(resolve) { From 5374f7173229c0bd0ff8d181752f1d0310b9e45c Mon Sep 17 00:00:00 2001 From: Johann Hoffmann Date: Mon, 20 Apr 2026 17:54:27 +0200 Subject: [PATCH 06/12] WIP replicating the lvMenu links & dropdowns accurately inside a tabulator5 actionButtons custom formatted column with all its kinks; basic links already working, design/layout definitely not finished, WIP working on how to get a sensible link dropdown in there; also still considering if this page even needs 2 seperate http requests at all --- .../controllers/api/frontend/v1/LvMenu.php | 2 +- public/js/components/Cis/Mylv/MyLv.js | 11 ++- public/js/components/Cis/Mylv/Table.js | 87 +++++++++++++++++-- 3 files changed, 90 insertions(+), 10 deletions(-) diff --git a/application/controllers/api/frontend/v1/LvMenu.php b/application/controllers/api/frontend/v1/LvMenu.php index 068eaf580..6bad00e1e 100644 --- a/application/controllers/api/frontend/v1/LvMenu.php +++ b/application/controllers/api/frontend/v1/LvMenu.php @@ -67,7 +67,7 @@ class LvMenu extends FHCAPI_Controller $lvMenuOptionList = $this->input->post('lvMenuOptionList', true); $result =[]; foreach($lvMenuOptionList as $lvMenuOptions){ - $lvMenu = $this->getLvMenu($lvMenuOptions['lvid'],$lvMenuOptions['studiensemester_kurzbz']); + $lvMenu = $this->getLvMenuInternal($lvMenuOptions['lvid'],$lvMenuOptions['studiensemester_kurzbz']); $result[$lvMenuOptions['lvid']]=$lvMenu; } diff --git a/public/js/components/Cis/Mylv/MyLv.js b/public/js/components/Cis/Mylv/MyLv.js index b4b5f1a6b..9f659a3cc 100644 --- a/public/js/components/Cis/Mylv/MyLv.js +++ b/public/js/components/Cis/Mylv/MyLv.js @@ -17,14 +17,12 @@ export default { studiensemester: null, lvs: {}, currentSemester: null, - lvMenues: null, mode: 'table' // TODO: load from local storage }; }, provide() { return { type: Vue.computed(() => this.type), - lvMenues: Vue.computed(() => this.lvMenues) } }, inject: ['isStudent', 'isMitarbeiter'], @@ -50,7 +48,14 @@ export default { this.firstLoad = false; this.$api.call(ApiAddons.getMultipleLvMenu(this.lvs[this.currentSemester].lvs, this.currentSemester)).then(res => { - this.lvMenues = res.data + if(res.data) { + Object.entries(res.data).forEach((entry) => { + // entry[0] -> key -> lvid + // entry[1] -> value -> menu + const lv = this.lvs[this.currentSemester].lvs.find(lv => lv.lehrveranstaltung_id == entry[0]) + lv.menu = entry[1] + }) + } }) }) diff --git a/public/js/components/Cis/Mylv/Table.js b/public/js/components/Cis/Mylv/Table.js index f1b624b01..09c1b83ab 100644 --- a/public/js/components/Cis/Mylv/Table.js +++ b/public/js/components/Cis/Mylv/Table.js @@ -25,9 +25,9 @@ export default { {title: Vue.computed(() => this.$p.t('lehre/studiengang')), field: 'sg_bezeichnung', widthGrow: 1}, {title: Vue.computed(() => this.$p.t('global/bezeichnung')), field: 'bezeichnung', widthGrow: 2}, {title: Vue.computed(() => this.$p.t('lehre/orgform')), field: 'orgform_kurzbz', widthGrow: 1}, - {title: Vue.computed(() => this.$p.t('admission/stg_kurz')), field: 'studiengang_kuerzel', widthGrow: 1}, - {title: Vue.computed(() => this.$p.t('global/actions')), - field: 'menu', formatter: this.actionFormatter, widthGrow: 1} + {title: Vue.computed(() => this.$p.t('lehre/kurzbz')), field: 'studiengang_kuerzel', widthGrow: 1}, + {title: Vue.computed(() => this.$p.t('global/actions')), headerSort: false, + field: 'menu', formatter: this.actionFormatter, widthGrow: 1, tooltip: this.spoofingFunc} ], persistence: false, persistenceID: "mylv_2026_04_17" @@ -47,23 +47,86 @@ export default { }, methods: { + spoofingFunc() { + // intentionally send empty tooltip info so tabulator tooltip doesnt get rendered but hover event propagates + // to individual button tooltips + return '' + }, + c4_target(menuItem) { + if (menuItem.c4_moodle_links?.length > 0) return null; + return menuItem.c4_target ?? null; + }, + c4_link(menuItem) { + if (!menuItem) return null; + if (Array.isArray(menuItem.c4_moodle_links) && menuItem.c4_moodle_links.length) { + return '#'; + } else { + return menuItem.c4_link ?? null; + } + }, handleUuidDefined(uuid) { this.tabulatorUuid = uuid }, tableResolve(resolve) { this.tableBuiltResolve = resolve }, - actionFormatter() { - return '' + actionFormatter(cell, formatterParams, onRendered) { + let container = document.createElement('div'); + container.className = "d-flex gap-2"; + + const data = cell.getData() + console.log(data) + if(data.menu && data.menu.length) { + + const calculatedMinWidth = data.menu.length * 120; + container.style.minWidth = `${calculatedMinWidth}px`; + const abbreviate = (str, limit = 12) => + str.length > limit + 3 ? `${str.slice(0, limit)}...` : str; + + data.menu.forEach((lvLink) => { + let button = document.createElement('button'); + button.className = 'btn btn-outline-secondary btn-action'; + if (!lvLink.c4_link) { + button.classList.add('unavailable'); + } + const icon = lvLink.c4_icon2 ?? 'fa-solid fa-pen-to-square' + button.id = lvLink.name + button.innerHTML = ''; + + button.title = lvLink.phrase ? this.$p.t(lvLink.phrase) : lvLink.name; + button.innerHTML += ''+abbreviate(button.title)+''; + button.addEventListener('click', (event) => { + // replicate a tag here + event.preventDefault(); + const url = this.c4_link(lvLink); + if (url) { + const target = lvLink.c4_target || '_blank'; + + if (target === '_blank') { + window.open(url, '_blank', 'noopener,noreferrer'); + } else { + window.location.href = url; + } + } else { + console.warn("Link is unavailable for:", lvLink.name); + } + }); + container.append(button); + }) + + } + + return container; }, async setupData() { this.$refs.mylvTable.tabulator.setData(this.lvs); }, async setupMounted() { - + // console.log('mounted pre table promise') this.tableBuiltPromise = new Promise(this.tableResolve) await this.tableBuiltPromise + console.log('mounted post table promise') this.setupData() const tableID = this.tabulatorUuid ? ('-' + this.tabulatorUuid) : '' @@ -85,6 +148,18 @@ export default { mounted() { this.setupMounted() }, + watch: { + lvs: { + handler(newVal, oldVal) { + console.log('watcher') + if(this.$refs.mylvTable) { + console.log('watcher inside if ref table clause') + this.$refs.mylvTable.tabulator.setData(newVal); + } + }, + deep: true + } + }, template: `
Date: Tue, 21 Apr 2026 14:05:19 +0200 Subject: [PATCH 07/12] dropdown menu formatter logic; dropdown menu from action col css fix; phrasen preload; WIP semesterstunden verification with the old page - hours dont add up yet; --- public/css/components/MyLv.css | 5 + public/js/components/Cis/Mylv/Table.js | 148 ++++++++++++++++++++----- 2 files changed, 126 insertions(+), 27 deletions(-) diff --git a/public/css/components/MyLv.css b/public/css/components/MyLv.css index 267e2f45e..1305cfc36 100644 --- a/public/css/components/MyLv.css +++ b/public/css/components/MyLv.css @@ -6,3 +6,8 @@ color: var(--fhc-myLv-disabled) !important; cursor: default; } + +/* adjustment to have bs5 dropdownmenus rendered properly over a tabulator table */ +.mylv-semester-table .tabulator-cell { + overflow: unset; +} \ No newline at end of file diff --git a/public/js/components/Cis/Mylv/Table.js b/public/js/components/Cis/Mylv/Table.js index 09c1b83ab..e16c96cc4 100644 --- a/public/js/components/Cis/Mylv/Table.js +++ b/public/js/components/Cis/Mylv/Table.js @@ -19,13 +19,16 @@ export default { mylvTableOptions: { height: Vue.ref(400), index: 'lehrveranstaltung_id', - layout: 'fitDataStretch', + layout: 'fitData', placeholder: this.$p.t('global/noDataAvailable'), columns: [ {title: Vue.computed(() => this.$p.t('lehre/studiengang')), field: 'sg_bezeichnung', widthGrow: 1}, {title: Vue.computed(() => this.$p.t('global/bezeichnung')), field: 'bezeichnung', widthGrow: 2}, {title: Vue.computed(() => this.$p.t('lehre/orgform')), field: 'orgform_kurzbz', widthGrow: 1}, {title: Vue.computed(() => this.$p.t('lehre/kurzbz')), field: 'studiengang_kuerzel', widthGrow: 1}, + {title: Vue.computed(() => this.$p.t('lehre/semesterstunden')), field: 'semesterstunden', + bottomCalc: this.semesterstundenCalc, bottomCalcFormatter: this.semesterstundenCalcFormatter, bottomCalcParams: this.semesterstundenParamLookup, + widthGrow: 1, visible: true}, {title: Vue.computed(() => this.$p.t('global/actions')), headerSort: false, field: 'menu', formatter: this.actionFormatter, widthGrow: 1, tooltip: this.spoofingFunc} ], @@ -47,6 +50,22 @@ export default { }, methods: { + semesterstundenCalcFormatter(cell) { + + return cell.getValue() + }, + semesterstundenParamLookup (values, data) { + const first = data[0] + debugger + return first ? first.anwesenheit + ' %' : '' + }, + semesterstundenCalc(values, data) { + let sum = 0 + values.forEach(val => { + sum += Number(val) + }) + return sum + }, spoofingFunc() { // intentionally send empty tooltip info so tabulator tooltip doesnt get rendered but hover event propagates // to individual button tooltips @@ -84,34 +103,109 @@ export default { str.length > limit + 3 ? `${str.slice(0, limit)}...` : str; data.menu.forEach((lvLink) => { - let button = document.createElement('button'); - button.className = 'btn btn-outline-secondary btn-action'; - if (!lvLink.c4_link) { - button.classList.add('unavailable'); - } - const icon = lvLink.c4_icon2 ?? 'fa-solid fa-pen-to-square' - button.id = lvLink.name - button.innerHTML = ''; - - button.title = lvLink.phrase ? this.$p.t(lvLink.phrase) : lvLink.name; - button.innerHTML += ''+abbreviate(button.title)+''; - button.addEventListener('click', (event) => { - // replicate a tag here - event.preventDefault(); - const url = this.c4_link(lvLink); - if (url) { - const target = lvLink.c4_target || '_blank'; + // render dropdown if we have a link and some some linklist + const hasDropdown = (lvLink.c4_moodle_links?.length || lvLink.c4_linkList?.length) && lvLink.c4_link; - if (target === '_blank') { - window.open(url, '_blank', 'noopener,noreferrer'); + if (hasDropdown) { + // button group + const group = document.createElement('div'); + group.className = 'btn-group'; + + // main action button + const button = document.createElement('a'); + button.className = 'fhc-body text-decoration-none text-truncate'; + if (!lvLink.c4_link) button.classList.add('unavailable'); + button.id = lvLink.name; + + const icon = lvLink.c4_icon2 ?? 'fa-solid fa-pen-to-square'; + const label = lvLink.phrase ? this.$p.t(lvLink.phrase) : lvLink.name; + button.title = label; + button.innerHTML = `${abbreviate(label)}`; + + button.addEventListener('click', (event) => { + event.preventDefault(); + const url = this.c4_link(lvLink); + if (url) { + const target = lvLink.c4_target || '_blank'; + if (target === '_blank') { + window.open(url, '_blank', 'noopener,noreferrer'); + } else { + window.location.href = url; + } } else { - window.location.href = url; + console.warn("Link is unavailable for:", lvLink.name); } - } else { - console.warn("Link is unavailable for:", lvLink.name); + }); + + // toggle button + const toggle = document.createElement('button'); + toggle.className = 'btn btn-sm dropdown-toggle dropdown-toggle-split border-0'; + toggle.type = 'button'; + toggle.dataset.bsToggle = 'dropdown'; // uses absolute position which gets clipped by tabulator + toggle.ariaExpanded = 'false'; + toggle.innerHTML = 'Toggle Dropdown'; + + // dropdown menu + const dropMenu = document.createElement('ul'); + dropMenu.className = 'dropdown-menu dropdown-menu p-0'; + dropMenu.style.zIndex = 9999 // over 9000 + dropMenu.style.position = 'fixed' + + // moodle links have priority to be dropdown items but both can be! + const items = lvLink.c4_moodle_links?.length + ? lvLink.c4_moodle_links.map(item => ({ text: item.lehrform, href: item.url })) + : lvLink.c4_linkList.map(([text, link]) => ({ text, href: link })); + + for(let i = 0; i < 10; i++) { + items.push({text: 'puffer', href: 'www.google.com'}) } - }); - container.append(button); + + items.forEach(({ text, href }) => { + const li = document.createElement('li'); + const a = document.createElement('a'); + a.className = 'dropdown-item border-bottom'; + a.href = href; + a.target = '#'; + a.textContent = text; + li.appendChild(a); + dropMenu.appendChild(li); + }); + + group.appendChild(button); + group.appendChild(toggle); + group.appendChild(dropMenu); + container.appendChild(group); + + } else { + // action button only + const button = document.createElement('a'); + button.className = 'fhc-body text-decoration-none text-truncate'; + if (!lvLink.c4_link) button.classList.add('unavailable'); + button.id = lvLink.name; + + const icon = lvLink.c4_icon2 ?? 'fa-solid fa-pen-to-square'; + const label = lvLink.phrase ? this.$p.t(lvLink.phrase) : lvLink.name; + button.title = label; + button.innerHTML = `${abbreviate(label)}`; + + button.addEventListener('click', (event) => { + event.preventDefault(); + const url = this.c4_link(lvLink); + if (url) { + const target = lvLink.c4_target || '_blank'; + if (target === '_blank') { + window.open(url, '_blank', 'noopener,noreferrer'); + } else { + window.location.href = url; + } + } else { + console.warn("Link is unavailable for:", lvLink.name); + } + }); + + container.appendChild(button); + } + }) } @@ -142,7 +236,7 @@ export default { } }, created() { - this.phrasenPromise = this.$p.loadCategory(['global']) + this.phrasenPromise = this.$p.loadCategory(['global', 'lehre', 'lvinfo']) this.phrasenPromise.then(()=> {this.phrasenResolved = true}) }, mounted() { @@ -161,7 +255,7 @@ export default { } }, template: ` -
+
Date: Tue, 21 Apr 2026 15:29:45 +0200 Subject: [PATCH 08/12] change lva fetch to select semesterstunden from a seperate aggregation subquery since they used to be listed by LE but current view should only show LVA to have the correct amounts displayed --- .../models/education/Lehrveranstaltung_model.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/application/models/education/Lehrveranstaltung_model.php b/application/models/education/Lehrveranstaltung_model.php index 1c32096a9..a03c2c5f2 100644 --- a/application/models/education/Lehrveranstaltung_model.php +++ b/application/models/education/Lehrveranstaltung_model.php @@ -1356,7 +1356,6 @@ class Lehrveranstaltung_model extends DB_Model lehre.tbl_lehrveranstaltung.lehrveranstaltung_id, lehre.tbl_lehrveranstaltung.bezeichnung, lehre.tbl_lehrveranstaltung.bezeichnung_english as bezeichnung_eng, - TRUNC(lehre.tbl_lehreinheitmitarbeiter.semesterstunden) as semesterstunden, lehre.tbl_lehrveranstaltung.farbe, lehre.tbl_lehrveranstaltung.lvinfo, lehre.tbl_lehrveranstaltung.benotung, @@ -1371,9 +1370,18 @@ class Lehrveranstaltung_model extends DB_Model JOIN lehre.tbl_lehrveranstaltung as lehrfach ON(tbl_lehreinheit.lehrfach_id=lehrfach.lehrveranstaltung_id) WHERE tbl_lehreinheit.studiensemester_kurzbz = ? - AND mitarbeiter_uid = ?) as distincted_by_lva_id + AND mitarbeiter_uid = ?) as distincted_by_lva_id + JOIN ( + SELECT lehrveranstaltung_id, TRUNC(SUM(lehre.tbl_lehreinheitmitarbeiter.semesterstunden)) as semesterstunden + FROM lehre.tbl_lehreinheit + JOIN lehre.tbl_lehreinheitmitarbeiter USING(lehreinheit_id) + JOIN lehre.tbl_lehrveranstaltung USING(lehrveranstaltung_id) + WHERE tbl_lehreinheit.studiensemester_kurzbz = ? + AND mitarbeiter_uid = ? + GROUP BY lehrveranstaltung_id + ) semesterstundenAggregatedSubquery USING(lehrveranstaltung_id) ORDER BY studiengang_kuerzel, semester, bezeichnung"; - return $this->execReadOnlyQuery($qry, [$sem_kurzbz, $mitarbeiter_uid]); + return $this->execReadOnlyQuery($qry, [$sem_kurzbz, $mitarbeiter_uid, $sem_kurzbz, $mitarbeiter_uid]); } } From 2c72f704d0011bbd539349af276489f73ec1a4d6 Mon Sep 17 00:00:00 2001 From: Johann Hoffmann Date: Tue, 21 Apr 2026 15:32:51 +0200 Subject: [PATCH 09/12] remove unnecessary bottom calc functions --- public/js/components/Cis/Mylv/Table.js | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/public/js/components/Cis/Mylv/Table.js b/public/js/components/Cis/Mylv/Table.js index e16c96cc4..c8b189eca 100644 --- a/public/js/components/Cis/Mylv/Table.js +++ b/public/js/components/Cis/Mylv/Table.js @@ -27,8 +27,7 @@ export default { {title: Vue.computed(() => this.$p.t('lehre/orgform')), field: 'orgform_kurzbz', widthGrow: 1}, {title: Vue.computed(() => this.$p.t('lehre/kurzbz')), field: 'studiengang_kuerzel', widthGrow: 1}, {title: Vue.computed(() => this.$p.t('lehre/semesterstunden')), field: 'semesterstunden', - bottomCalc: this.semesterstundenCalc, bottomCalcFormatter: this.semesterstundenCalcFormatter, bottomCalcParams: this.semesterstundenParamLookup, - widthGrow: 1, visible: true}, + bottomCalc: this.semesterstundenCalc, widthGrow: 1, visible: true}, {title: Vue.computed(() => this.$p.t('global/actions')), headerSort: false, field: 'menu', formatter: this.actionFormatter, widthGrow: 1, tooltip: this.spoofingFunc} ], @@ -50,15 +49,6 @@ export default { }, methods: { - semesterstundenCalcFormatter(cell) { - - return cell.getValue() - }, - semesterstundenParamLookup (values, data) { - const first = data[0] - debugger - return first ? first.anwesenheit + ' %' : '' - }, semesterstundenCalc(values, data) { let sum = 0 values.forEach(val => { From 501bae585aa62d660c3c201f2e58d6b7742c5a00 Mon Sep 17 00:00:00 2001 From: Johann Hoffmann Date: Tue, 21 Apr 2026 16:59:10 +0200 Subject: [PATCH 10/12] WIP --- public/js/components/Cis/Mylv/Table.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/public/js/components/Cis/Mylv/Table.js b/public/js/components/Cis/Mylv/Table.js index c8b189eca..3584ba0e4 100644 --- a/public/js/components/Cis/Mylv/Table.js +++ b/public/js/components/Cis/Mylv/Table.js @@ -79,7 +79,7 @@ export default { tableResolve(resolve) { this.tableBuiltResolve = resolve }, - actionFormatter(cell, formatterParams, onRendered) { + actionFormatter(cell) { let container = document.createElement('div'); container.className = "d-flex gap-2"; @@ -138,8 +138,6 @@ export default { // dropdown menu const dropMenu = document.createElement('ul'); dropMenu.className = 'dropdown-menu dropdown-menu p-0'; - dropMenu.style.zIndex = 9999 // over 9000 - dropMenu.style.position = 'fixed' // moodle links have priority to be dropdown items but both can be! const items = lvLink.c4_moodle_links?.length From c36f259571bd218c6271aba0492c872880f6115d Mon Sep 17 00:00:00 2001 From: Johann Hoffmann Date: Tue, 21 Apr 2026 17:40:24 +0200 Subject: [PATCH 11/12] revert back to 1 http request per lva menu list for performance reasons since usually 1-2 lva are slow and the rest load quickly; many menu_lv.inc scripts would need to be loaded via require instead of require_once but that breaks some of them; only table height calc on watcher; why does an english teacher need a mathe online link?; --- public/js/components/Cis/Mylv/MyLv.js | 34 +++++++++++++++++++------- public/js/components/Cis/Mylv/Table.js | 10 ++++++++ 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/public/js/components/Cis/Mylv/MyLv.js b/public/js/components/Cis/Mylv/MyLv.js index 9f659a3cc..b87bcd749 100644 --- a/public/js/components/Cis/Mylv/MyLv.js +++ b/public/js/components/Cis/Mylv/MyLv.js @@ -47,16 +47,32 @@ export default { this.lvs[this.currentSemester].lvs = res.data.retval || []; this.firstLoad = false; - this.$api.call(ApiAddons.getMultipleLvMenu(this.lvs[this.currentSemester].lvs, this.currentSemester)).then(res => { - if(res.data) { - Object.entries(res.data).forEach((entry) => { - // entry[0] -> key -> lvid - // entry[1] -> value -> menu - const lv = this.lvs[this.currentSemester].lvs.find(lv => lv.lehrveranstaltung_id == entry[0]) - lv.menu = entry[1] - }) - } + // pretty slow to load all at once if one lva has a weird moodle / lvinfo situation + // that multiplies the loadtime for everything by a factor of 3-5 + + // this.$api.call(ApiAddons.getMultipleLvMenu(this.lvs[this.currentSemester].lvs, this.currentSemester)).then(res => { + // if(res.data) { + // Object.entries(res.data).forEach((entry) => { + // // entry[0] -> key -> lvid + // // entry[1] -> value -> menu + // const lv = this.lvs[this.currentSemester].lvs.find(lv => lv.lehrveranstaltung_id == entry[0]) + // lv.menu = entry[1] + // }) + // } + // }) + this.lvs[this.currentSemester].lvs.forEach(lv=>{ + + this.$api.call(ApiAddons.getLvMenu(lv.lehrveranstaltung_id, this.currentSemester)).then(res => { + if(res.data) { + + const lvProp = this.lvs[this.currentSemester].lvs.find(lv2 => lv2.lehrveranstaltung_id == lv.lehrveranstaltung_id) + lvProp.menu = res.data + + } + }) + }) + }) } diff --git a/public/js/components/Cis/Mylv/Table.js b/public/js/components/Cis/Mylv/Table.js index 3584ba0e4..6f0232c64 100644 --- a/public/js/components/Cis/Mylv/Table.js +++ b/public/js/components/Cis/Mylv/Table.js @@ -237,6 +237,16 @@ export default { if(this.$refs.mylvTable) { console.log('watcher inside if ref table clause') this.$refs.mylvTable.tabulator.setData(newVal); + + const tableID = this.tabulatorUuid ? ('-' + this.tabulatorUuid) : '' + const tableDataSet = document.getElementById('filterTableDataset' + tableID); + if(!tableDataSet) return + const rect = tableDataSet.getBoundingClientRect(); + + const h = window.visualViewport.height - rect.top - 100 + if(this.$refs.mylvTable) { + this.$refs.mylvTable.$refs.table.style.setProperty('height', h+'px') + } } }, deep: true From e86e7f0bd8cb116a3e2c69b7e56fa49e674e1c29 Mon Sep 17 00:00:00 2001 From: Johann Hoffmann Date: Thu, 23 Apr 2026 15:59:38 +0200 Subject: [PATCH 12/12] manage default myLv layout mode via localStorage; actions row takes remaining space via fitDataStretch and wraps around into new row if action buttons take up too much space; added tabulator persistence on TableBuilt event; slight watcher adjustments to combat race conditions; loading spinner while tabulatorUuid has not been defined yet -> maybe worth improving but seems to work fine; --- .../controllers/api/frontend/v1/LvMenu.php | 4 + public/css/components/MyLv.css | 5 + public/js/components/Cis/Mylv/MyLv.js | 16 +- .../Cis/Mylv/Semester/Studiengang/Lv.js | 12 +- public/js/components/Cis/Mylv/Table.js | 277 +++++++++++------- 5 files changed, 190 insertions(+), 124 deletions(-) diff --git a/application/controllers/api/frontend/v1/LvMenu.php b/application/controllers/api/frontend/v1/LvMenu.php index 6bad00e1e..27cbba1c2 100644 --- a/application/controllers/api/frontend/v1/LvMenu.php +++ b/application/controllers/api/frontend/v1/LvMenu.php @@ -62,6 +62,10 @@ class LvMenu extends FHCAPI_Controller /** * alternative function to get multiple lvMenus with a single http request + * not yet working as intended as the menu_lv.inc.php scripts called by the + * lvMenuBuild event have logic coupled to require_once import which results in + * a wrong logic after the first invocation -> faulty results for lvinfo, moodle + * and several others */ public function getMultipleLvMenu(){ $lvMenuOptionList = $this->input->post('lvMenuOptionList', true); diff --git a/public/css/components/MyLv.css b/public/css/components/MyLv.css index 1305cfc36..432591959 100644 --- a/public/css/components/MyLv.css +++ b/public/css/components/MyLv.css @@ -10,4 +10,9 @@ /* adjustment to have bs5 dropdownmenus rendered properly over a tabulator table */ .mylv-semester-table .tabulator-cell { overflow: unset; +} + +.mylv-semester-table .tabulator-cell .action-col { + /*min-height: 2.5rem;*/ + align-items: flex-start; /* so wrapped rows don't stretch vertically */ } \ No newline at end of file diff --git a/public/js/components/Cis/Mylv/MyLv.js b/public/js/components/Cis/Mylv/MyLv.js index b87bcd749..8256d87eb 100644 --- a/public/js/components/Cis/Mylv/MyLv.js +++ b/public/js/components/Cis/Mylv/MyLv.js @@ -17,7 +17,7 @@ export default { studiensemester: null, lvs: {}, currentSemester: null, - mode: 'table' // TODO: load from local storage + mode: localStorage.getItem('myLvaDefaultMode') ?? 'cards' }; }, provide() { @@ -46,20 +46,7 @@ export default { axios.get(FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/components/Cis/Mylv/Lvs/' + this.currentSemester).then(res => { this.lvs[this.currentSemester].lvs = res.data.retval || []; this.firstLoad = false; - - // pretty slow to load all at once if one lva has a weird moodle / lvinfo situation - // that multiplies the loadtime for everything by a factor of 3-5 - // this.$api.call(ApiAddons.getMultipleLvMenu(this.lvs[this.currentSemester].lvs, this.currentSemester)).then(res => { - // if(res.data) { - // Object.entries(res.data).forEach((entry) => { - // // entry[0] -> key -> lvid - // // entry[1] -> value -> menu - // const lv = this.lvs[this.currentSemester].lvs.find(lv => lv.lehrveranstaltung_id == entry[0]) - // lv.menu = entry[1] - // }) - // } - // }) this.lvs[this.currentSemester].lvs.forEach(lv=>{ this.$api.call(ApiAddons.getLvMenu(lv.lehrveranstaltung_id, this.currentSemester)).then(res => { @@ -108,6 +95,7 @@ export default { }, methods: { clickMode(evt, mode) { + localStorage.setItem('myLvaDefaultMode', mode) this.mode = mode }, prevSem() { diff --git a/public/js/components/Cis/Mylv/Semester/Studiengang/Lv.js b/public/js/components/Cis/Mylv/Semester/Studiengang/Lv.js index c1afd65d0..c00eeced1 100644 --- a/public/js/components/Cis/Mylv/Semester/Studiengang/Lv.js +++ b/public/js/components/Cis/Mylv/Semester/Studiengang/Lv.js @@ -28,13 +28,13 @@ export default { ects: String, incoming: Number, positiv: Boolean, - note_index: String + note_index: String, + menu: [Array, String] }, data: () => { return { pruefungenData: null, info: null, - menu: null, preselectedMenuItem: null, } }, @@ -104,11 +104,6 @@ export default { }); } }, - watch:{ - studien_semester(newValue){ - this.fetchMenu(this.lehrveranstaltung_id, newValue); - } - }, created() { if(this.type == 'student') { this.$api @@ -119,9 +114,6 @@ export default { }); } }, - mounted() { - this.fetchMenu(this.lehrveranstaltung_id, this.studien_semester); - }, template: /*html*/`
diff --git a/public/js/components/Cis/Mylv/Table.js b/public/js/components/Cis/Mylv/Table.js index 6f0232c64..18f169e8f 100644 --- a/public/js/components/Cis/Mylv/Table.js +++ b/public/js/components/Cis/Mylv/Table.js @@ -13,40 +13,34 @@ export default { return { phrasenPromise: null, phrasenResolved: false, - tabulatorUuid: Vue.ref(0), + tabulatorUuid: null, tableBuiltResolve: null, tableBuiltPromise: null, mylvTableOptions: { height: Vue.ref(400), index: 'lehrveranstaltung_id', - layout: 'fitData', + layout: 'fitDataStretch', placeholder: this.$p.t('global/noDataAvailable'), columns: [ - {title: Vue.computed(() => this.$p.t('lehre/studiengang')), field: 'sg_bezeichnung', widthGrow: 1}, - {title: Vue.computed(() => this.$p.t('global/bezeichnung')), field: 'bezeichnung', widthGrow: 2}, - {title: Vue.computed(() => this.$p.t('lehre/orgform')), field: 'orgform_kurzbz', widthGrow: 1}, - {title: Vue.computed(() => this.$p.t('lehre/kurzbz')), field: 'studiengang_kuerzel', widthGrow: 1}, - {title: Vue.computed(() => this.$p.t('lehre/semesterstunden')), field: 'semesterstunden', - bottomCalc: this.semesterstundenCalc, widthGrow: 1, visible: true}, - {title: Vue.computed(() => this.$p.t('global/actions')), headerSort: false, + {title: Vue.computed(() => this.$capitalize(this.$p.t('lehre/studiengang'))), field: 'sg_bezeichnung', widthGrow: 1}, + {title: Vue.computed(() => this.$capitalize(this.$p.t('global/bezeichnung'))), field: 'bezeichnung', widthGrow: 2}, + {title: Vue.computed(() => this.$capitalize(this.$p.t('lehre/orgform'))), field: 'orgform_kurzbz', widthGrow: 1}, + {title: Vue.computed(() => this.$capitalize(this.$p.t('lehre/kurzbz'))), field: 'studiengang_kuerzel', widthGrow: 1}, + {title: Vue.computed(() => this.$capitalize(this.$p.t('lehre/semesterstunden'))), field: 'semesterstunden', + bottomCalc: this.semesterstundenCalc, widthGrow: 1, visible: false}, + {title: Vue.computed(() => this.$capitalize(this.$p.t('global/actions'))), headerSort: false, field: 'menu', formatter: this.actionFormatter, widthGrow: 1, tooltip: this.spoofingFunc} ], persistence: false, persistenceID: "mylv_2026_04_17" }, mylvTableEventHandlers: [ - { - event: "tableBuilt", - handler: async () => { - this.tableBuiltResolve() - } - } + ] } }, computed: { ready() { return this.lvs !== null; }, - }, methods: { semesterstundenCalc(values, data) { @@ -61,10 +55,6 @@ export default { // to individual button tooltips return '' }, - c4_target(menuItem) { - if (menuItem.c4_moodle_links?.length > 0) return null; - return menuItem.c4_target ?? null; - }, c4_link(menuItem) { if (!menuItem) return null; if (Array.isArray(menuItem.c4_moodle_links) && menuItem.c4_moodle_links.length) { @@ -84,14 +74,10 @@ export default { container.className = "d-flex gap-2"; const data = cell.getData() - console.log(data) if(data.menu && data.menu.length) { - - const calculatedMinWidth = data.menu.length * 120; - container.style.minWidth = `${calculatedMinWidth}px`; - const abbreviate = (str, limit = 12) => - str.length > limit + 3 ? `${str.slice(0, limit)}...` : str; + container.className = "d-flex flex-wrap gap-2" + data.menu.forEach((lvLink) => { // render dropdown if we have a link and some some linklist const hasDropdown = (lvLink.c4_moodle_links?.length || lvLink.c4_linkList?.length) && lvLink.c4_link; @@ -102,30 +88,7 @@ export default { group.className = 'btn-group'; // main action button - const button = document.createElement('a'); - button.className = 'fhc-body text-decoration-none text-truncate'; - if (!lvLink.c4_link) button.classList.add('unavailable'); - button.id = lvLink.name; - - const icon = lvLink.c4_icon2 ?? 'fa-solid fa-pen-to-square'; - const label = lvLink.phrase ? this.$p.t(lvLink.phrase) : lvLink.name; - button.title = label; - button.innerHTML = `${abbreviate(label)}`; - - button.addEventListener('click', (event) => { - event.preventDefault(); - const url = this.c4_link(lvLink); - if (url) { - const target = lvLink.c4_target || '_blank'; - if (target === '_blank') { - window.open(url, '_blank', 'noopener,noreferrer'); - } else { - window.location.href = url; - } - } else { - console.warn("Link is unavailable for:", lvLink.name); - } - }); + const button= this.createActionButton(lvLink) // toggle button const toggle = document.createElement('button'); @@ -143,10 +106,7 @@ export default { const items = lvLink.c4_moodle_links?.length ? lvLink.c4_moodle_links.map(item => ({ text: item.lehrform, href: item.url })) : lvLink.c4_linkList.map(([text, link]) => ({ text, href: link })); - - for(let i = 0; i < 10; i++) { - items.push({text: 'puffer', href: 'www.google.com'}) - } + items.forEach(({ text, href }) => { const li = document.createElement('li'); @@ -165,33 +125,7 @@ export default { container.appendChild(group); } else { - // action button only - const button = document.createElement('a'); - button.className = 'fhc-body text-decoration-none text-truncate'; - if (!lvLink.c4_link) button.classList.add('unavailable'); - button.id = lvLink.name; - - const icon = lvLink.c4_icon2 ?? 'fa-solid fa-pen-to-square'; - const label = lvLink.phrase ? this.$p.t(lvLink.phrase) : lvLink.name; - button.title = label; - button.innerHTML = `${abbreviate(label)}`; - - button.addEventListener('click', (event) => { - event.preventDefault(); - const url = this.c4_link(lvLink); - if (url) { - const target = lvLink.c4_target || '_blank'; - if (target === '_blank') { - window.open(url, '_blank', 'noopener,noreferrer'); - } else { - window.location.href = url; - } - } else { - console.warn("Link is unavailable for:", lvLink.name); - } - }); - - container.appendChild(button); + container.appendChild(this.createActionButton(lvLink)); } }) @@ -200,15 +134,150 @@ export default { return container; }, + createActionButton(lvLink){ + const button = document.createElement('a'); + button.className = 'fhc-body text-decoration-none text-truncate'; + if (!lvLink.c4_link) button.classList.add('unavailable'); + button.id = `${lvLink.name}_${lvLink.lehrveranstaltung_id}`; + + const icon = lvLink.c4_icon2 ?? 'fa-solid fa-pen-to-square'; + const label = lvLink.phrase ? this.$p.t(lvLink.phrase) : lvLink.name; + button.title = label; + button.innerHTML = `${label}`; + + button.addEventListener('click', (event) => { + event.preventDefault(); + const url = this.c4_link(lvLink); + if (url) { + const target = lvLink.c4_target || '_blank'; + if (target === '_blank') { + window.open(url, '_blank', 'noopener,noreferrer'); + } else { + window.location.href = url; + } + } else { + console.warn("Link is unavailable for:", lvLink.name); + } + }); + return button + }, + loadState() { + return JSON.parse(localStorage.getItem(this.mylvTableOptions.persistenceID) || "null"); + }, + saveState(table) { + // avoid storing state after first restore part happened + if(!this.stateRestored) return + const rawLayout = table.getColumnLayout(); + const state = { + columns: rawLayout.map(col => ({ + field: col.field, + visible: col.visible, + width: col.width, + })), + sort: table.getSorters().map(s => ({ + field: s.field, + dir: s.dir, + })), + filters: table.getFilters(), + headerFilters: table.getHeaderFilters() + }; + + localStorage.setItem(this.mylvTableOptions.persistenceID, JSON.stringify(state)); + }, + handleTableBuilt() { + const table = this.$refs.mylvTable.tabulator + + this.tableBuiltResolve() + + table.on("columnMoved", () => { + this.saveState(table); + }); + + table.on("columnResized", () => { + this.saveState(table); + }); + + table.on("columnVisibilityChanged", () => { + this.saveState(table); + }); + + table.on("filterChanged", () => { + this.saveState(table); + }); + + table.on("headerFilterChanged", () => { + this.saveState(table); + }); + + table.on("dataSorted", () => { + this.saveState(table); + }); + + table.on("columnSorted", () => { + this.saveState(table); + }); + + table.on("sortersChanged", () => { + this.saveState(table); + }); + + const saved = this.loadState(); + + table.on("renderComplete", () => { + if(!this.stateRestored) { + + if (saved?.columns && !this.colLayoutRestored) { + const layout = saved.columns.map(col => ({ + field: col.field, + width: col.width, + visible: col.visible, + // add more if needed, but keep it simple + })); + + table.setColumnLayout(layout); + + this.colLayoutRestored = true; + } + + if (saved?.filters && !this.filtersRestored) { + this.filtersRestored = true // instantly avoid retriggers + table.setFilter(saved.filters); + } + if (saved?.headerFilters && !this.headerFiltersRestored) { + this.headerFiltersRestored = true // instantly avoid retriggers + for (let hf of saved.headerFilters) { + table.setHeaderFilterValue(hf.field, hf.value); + } + } + + if (saved?.sort?.length && !this.sortRestored) { + this.sortRestored = true; + + setTimeout(() => { + const sortList = saved.sort.map(s => { + const col = table.columnManager.findColumn(s.field); + if (!col) { + return null; + } + return { column: col, dir: s.dir }; + }).filter(Boolean); + + table.setSort(sortList); + }, 100); + } + this.stateRestored = true + + } + + }); + }, async setupData() { this.$refs.mylvTable.tabulator.setData(this.lvs); }, async setupMounted() { - // console.log('mounted pre table promise') this.tableBuiltPromise = new Promise(this.tableResolve) await this.tableBuiltPromise - - console.log('mounted post table promise') + this.setupData() const tableID = this.tabulatorUuid ? ('-' + this.tabulatorUuid) : '' @@ -216,9 +285,13 @@ export default { if(!tableDataSet) return const rect = tableDataSet.getBoundingClientRect(); - const h = window.visualViewport.height - rect.top - 100 + const h = window.visualViewport.height - rect.top - 50 if(this.$refs.mylvTable) { this.$refs.mylvTable.$refs.table.style.setProperty('height', h+'px') + + // necessary so the wrapping action row resolves to the full rowHeight required + // without the redraw here actions past the initial rowHeight would be clipped off + this.$refs.mylvTable.tabulator.redraw(true) } } @@ -232,21 +305,20 @@ export default { }, watch: { lvs: { - handler(newVal, oldVal) { - console.log('watcher') + async handler(newVal) { + await this.tableBuiltPromise; + if(!this.$refs.mylvTable?.tabulator) return + + this.$refs.mylvTable.tabulator.setData(newVal); + + const tableID = this.tabulatorUuid ? ('-' + this.tabulatorUuid) : '' + const tableDataSet = document.getElementById('filterTableDataset' + tableID); + if(!tableDataSet) return + const rect = tableDataSet.getBoundingClientRect(); + + const h = window.visualViewport.height - rect.top - 50 if(this.$refs.mylvTable) { - console.log('watcher inside if ref table clause') - this.$refs.mylvTable.tabulator.setData(newVal); - - const tableID = this.tabulatorUuid ? ('-' + this.tabulatorUuid) : '' - const tableDataSet = document.getElementById('filterTableDataset' + tableID); - if(!tableDataSet) return - const rect = tableDataSet.getBoundingClientRect(); - - const h = window.visualViewport.height - rect.top - 100 - if(this.$refs.mylvTable) { - this.$refs.mylvTable.$refs.table.style.setProperty('height', h+'px') - } + this.$refs.mylvTable.$refs.table.style.setProperty('height', h+'px') } }, deep: true @@ -261,8 +333,13 @@ export default { ref="mylvTable" :tabulator-options="mylvTableOptions" :tabulator-events="mylvTableEventHandlers" + @tableBuilt="handleTableBuilt" tableOnly :sideMenu="false" /> -
` +
+
+ +
+ ` }; \ No newline at end of file