Compare commits

...

181 Commits

Author SHA1 Message Date
adisposkofh f45782aeef removed redundant console log 2026-06-02 12:49:11 +02:00
adisposkofh c2faff5a03 minor fix 2026-06-02 11:52:13 +02:00
adisposkofh 1cff18112b removed auto-ban on sunday room reservations 2026-06-02 11:09:13 +02:00
adisposkofh 195b99e588 minor fix re: calendar event timeslot display 2026-06-01 13:52:54 +02:00
Harald Bamberger 6019489ef1 Merge branch 'cis40_2026-02_rc' into cis40_2026-05_ma_rc 2026-05-29 13:38:58 +02:00
adisposkofh fc79f92796 added 'add reservation'/'reservation not allowed' tooltips in calendar 2026-05-27 15:35:45 +02:00
adisposkofh 8e569a9ccd displaying forbidden room reservation slots 2026-05-27 10:43:28 +02:00
adisposkofh d7b2964e4e obsolete use of viewData object from cisRouterView 2026-05-26 17:33:38 +02:00
adisposkofh fa91e204f0 always displaying timeslot on calendar event in list view 2026-05-26 12:02:08 +02:00
adisposkofh 23506430b1 initializing stg org lv plan with provided url params 2026-05-26 09:30:50 +02:00
adisposkofh fa58635a22 positioning of profile cards 'quick links' and 'calendar sync' 2026-05-26 09:29:10 +02:00
Harald Bamberger 7f630f24d5 StundenplanLib: check array index access before 2026-05-26 07:54:41 +02:00
Harald Bamberger b0f90cafb6 Merge branch 'cis40_2026-02_rc' into cis40_2026-05_ma_rc 2026-05-22 10:36:54 +02:00
Harald Bamberger c0c57ba378 remove irregular files 2026-05-22 10:36:28 +02:00
Harald Bamberger d56d1bc2bd Merge branch 'feature-69187/CIS_MA_Pruefungsprotokolle' into cis40_2026-05_ma_rc 2026-05-19 16:17:35 +02:00
Harald Bamberger 45eca862ac use table alias 2026-05-19 16:17:14 +02:00
Harald Bamberger 9f738f4871 Merge branch 'feature-69187/CIS_MA_Pruefungsprotokolle' into cis40_2026-05_ma_rc 2026-05-19 16:11:37 +02:00
Harald Bamberger 3533a3fd4b add missing comma 2026-05-19 16:11:08 +02:00
Harald Bamberger 672acfe8fe Merge branch 'cis40_2026-02_rc' into cis40_2026-05_ma_rc 2026-05-18 16:49:53 +02:00
adisposkofh 6a4db90897 Merge branch 'cis40_2026-05_ma_rc' of github.com:FH-Complete/FHC-Core into cis40_2026-05_ma_rc 2026-05-18 11:22:05 +02:00
Harald Bamberger 449537ef77 Merge branch 'master' into cis40_2026-02_rc 2026-05-18 07:26:37 +02:00
adisposkofh 82587a70be restored missing provided properties 2026-05-15 18:40:42 +02:00
adisposkofh d16120f650 Merge branch 'cis40_2026-02_rc' of github.com:FH-Complete/FHC-Core into cis40_2026-02_rc 2026-05-15 17:47:01 +02:00
Harald Bamberger d0ef9ca96c Merge branch 'cis40_2026-02_rc' into cis40_2026-05_ma_rc 2026-05-13 21:11:16 +02:00
Harald Bamberger 12d8c8447b Merge branch 'feature-68957/CIS4_Dashboard_Bookmark_Widget_Sort_Tags' into cis40_2026-02_rc 2026-05-13 21:10:27 +02:00
Harald Bamberger 905cd46942 add condition converting column tbl_bookmark.tag to jsonb 2026-05-13 21:09:18 +02:00
adisposkofh 625ffe12ce added 'info' marker to clickable multiple grades 2026-05-13 16:46:23 +02:00
Harald Bamberger cb7a0f7669 Merge branch 'feature-70376/Lohnguide' 2026-05-13 11:53:14 +02:00
Harald Bamberger 68d97a5e97 handle case where old value or new value and not both are null explicitly in markDirty Method 2026-05-13 11:42:25 +02:00
Harald Bamberger d27071528f revert change to comparision in markDirty Method 2026-05-13 11:16:18 +02:00
Harald Bamberger 17772c3738 Merge branch 'master' into feature-70376/Lohnguide 2026-05-13 11:15:07 +02:00
adisposkofh a5d5d42ba3 fixed issue missing event attributes in stundenplan library 2026-05-12 13:19:57 +02:00
adisposkofh 15441a46f7 initializing stg org lv plan with provided url params 2026-05-11 17:35:09 +02:00
adisposkofh a9a56bb1e9 fixed issue with parsing isEditable from fetched profile view data 2026-05-11 17:03:33 +02:00
adisposkofh e840be84eb positioning of profile cards 'quick links' and 'calendar sync' 2026-05-11 16:47:41 +02:00
adisposkofh 0b40455e3c only displaying avg grade for students 2026-05-11 15:49:20 +02:00
adisposkofh d9ea5a95af fixed issue with undefined reservierung attributes 2026-05-11 15:04:32 +02:00
Harald Bamberger 23502d4fab Merge branch 'cis40_2026-02_rc' into cis40_2026-05_ma_rc 2026-05-11 13:54:38 +02:00
adisposkofh 772f35c6ba reworked sidenav toggler 2026-05-11 13:00:50 +02:00
adisposkofh 4b22f939b5 reworked searchbar collapse for component reusability 2026-05-11 10:24:18 +02:00
adisposkofh 7b46a15752 minor fix 2026-05-11 09:58:33 +02:00
adisposkofh bf3d6275d4 header rework: single fixed element, ordering, small screen cutoff 2026-05-08 16:31:55 +02:00
adisposkofh dd2fd6421b clean lv plan reactions to ismobile changes 2026-05-07 17:23:28 +02:00
adisposkofh 3b99a14b47 clean lv plan reactions to ismobile changes 2026-05-07 17:18:33 +02:00
adisposkofh 1d3d067b44 making isMobile property reactive to window resizing 2026-05-07 16:39:07 +02:00
adisposkofh d3ceed32c6 compacting all events in mobile monthly lv plan 2026-05-07 15:15:50 +02:00
adisposkofh 780890fbdd made search icons white in both dark and light mode 2026-05-07 14:19:47 +02:00
adisposkofh 72aed76857 added border to compacted events in lv plan for color contrasting 2026-05-07 14:06:23 +02:00
Harald Bamberger 3690babf62 Merge branch 'cis40_2026-02_rc' into cis40_2026-05_ma_rc 2026-05-07 14:01:50 +02:00
adisposkofh 229882e8d8 Merge branch 'cis40_2026-02_rc' of github.com:FH-Complete/FHC-Core into cis40_2026-02_rc 2026-05-07 13:19:49 +02:00
adisposkofh 1e184d36fc resolved prop type/injection-related warnings in 'my courses' 2026-05-07 11:10:01 +02:00
Harald Bamberger 9ca7ff73f4 Merge branch 'feature-60873/GesamtnoteneingabeCis4' into cis40_2026-05_ma_rc 2026-05-06 18:36:27 +02:00
Harald Bamberger c56064d189 js import add missing file extension 2026-05-06 18:36:05 +02:00
Harald Bamberger 424495c636 Merge branch 'feature-70747/Zeitsperren_auf_VueJs_Portieren' into cis40_2026-05_ma_rc 2026-05-06 18:31:46 +02:00
Harald Bamberger 9c7e98f1cb js import add missing file extension 2026-05-06 18:31:26 +02:00
Harald Bamberger 23edcf3aa7 Merge branch 'cis40_2026-02_rc' into cis40_2026-05_ma_rc 2026-05-06 17:35:48 +02:00
Harald Bamberger 90e933de00 Merge branch 'master' into cis40_2026-02_rc 2026-05-06 17:35:28 +02:00
adisposkofh 14372a6fce fixed multiple issues in news 2026-05-06 16:21:20 +02:00
Harald Bamberger bbb4f8a01c Merge branch 'bug-76146/studvw_karteireiter_dokumente_akzeptiert_eintraege_ohne_vorhandenes_dokument' 2026-05-06 16:13:50 +02:00
adisposkofh 80306dadf7 fixed issue with missing nullsafe operators 2026-05-06 15:29:33 +02:00
adisposkofh adba14f6e7 avoiding search when search string is empty 2026-05-06 13:49:43 +02:00
adisposkofh 57e901be27 other_lv_plan permission on reservations 2026-05-06 13:41:16 +02:00
adisposkofh d38641e312 displaying student personal identity number and matriculation number 2026-05-06 13:22:38 +02:00
Harald Bamberger fdbb93a5c5 bugfix download booking receipt failed. only fetch oehbeitrag from bis.tbl_oehbeitrag if a user is logged in 2026-05-05 14:36:07 +02:00
Harald Bamberger b7e48633ab Merge branch 'master' into bug-76146/studvw_karteireiter_dokumente_akzeptiert_eintraege_ohne_vorhandenes_dokument 2026-05-05 13:33:38 +02:00
Harald Bamberger 04dc1eb07b Merge branch 'bug-76519/StudVW_Messages_Table_column_Stati' 2026-05-05 13:04:58 +02:00
Harald Bamberger 50b229090b prefetch phrases and then render filter component instead of redrawing the table 2026-05-05 13:04:11 +02:00
Harald Bamberger ca19306b72 Merge branch 'cis40_2026-02_rc' into cis40_2026-05_ma_rc 2026-05-05 12:36:56 +02:00
Harald Bamberger 86dc002fa6 add composer script to symlink vendor folder in public to be able to import esm modules from vendor without breaking rewrite mechanism for cache token in urlpath 2026-05-05 12:36:37 +02:00
Harald Bamberger 2d27a998c4 Merge branch 'bug-76109/VVW_Details_und_Status_not_Loading' 2026-05-05 11:17:49 +02:00
Harald Bamberger 090e535466 add header filters, increase height of tables, use correct category for phrase lehreinheit_id 2026-05-05 11:04:48 +02:00
Harald Bamberger c4d35181db Merge branch 'master' into bug-76109/VVW_Details_und_Status_not_Loading 2026-05-05 09:01:49 +02:00
Harald Bamberger 66c0c14748 Merge branch 'cis40_2026-02_rc' into cis40_2026-05_ma_rc 2026-05-05 08:53:34 +02:00
Harald Bamberger 453fc209b8 Merge branch 'master' into cis40_2026-02_rc 2026-05-05 08:53:07 +02:00
Harald Bamberger 0ac6ef4599 Merge branch 'feature-62607/konto_oh_beitrag_betrag_aus_eigener_tabelle' 2026-05-05 08:46:22 +02:00
Werner Masik 7f13c128f1 allow null value for vordienstzeit; changed comparison in markDirty to !== (because of 0 vs. null issue) 2026-05-04 20:35:51 +02:00
Harald Bamberger cffa493984 Merge branch 'feature-68301/cis4_ma_raumreservierung' into cis40_2026-05_ma_rc 2026-05-04 18:17:53 +02:00
Harald Bamberger 9030cdcc76 Merge branch 'feature-70747/Zeitsperren_auf_VueJs_Portieren' into cis40_2026-05_ma_rc 2026-05-04 18:06:21 +02:00
Harald Bamberger c3ef487a6f Merge branch 'feature-76148/MyLvaMA' into cis40_2026-05_ma_rc 2026-05-04 17:59:51 +02:00
Harald Bamberger ac0eddf4c7 Merge branch 'feature-69187/CIS_MA_Pruefungsprotokolle' into cis40_2026-05_ma_rc 2026-05-04 17:05:36 +02:00
Harald Bamberger e373e797f4 Merge branch 'feature-60873/GesamtnoteneingabeCis4' into cis40_2026-05_ma_rc 2026-05-04 17:01:44 +02:00
Harald Bamberger 382244035b Merge branch 'feature-68610/CIS4_Projektabgabe' into cis40_2026-05_ma_rc 2026-05-04 16:09:33 +02:00
Harald Bamberger b9c8c71274 Merge branch 'feature-61236/Cis4MenuErweiterung' into cis40_2026-05_ma_rc 2026-05-04 16:01:18 +02:00
Harald Bamberger cb60ddcc94 Merge branch 'master' into feature-62607/konto_oh_beitrag_betrag_aus_eigener_tabelle 2026-05-04 15:41:52 +02:00
Harald Bamberger bd4ced9559 bugfix: comma as decimal separator prevents saving booking, bugfix messages tinymce not resizeable 2026-05-04 14:39:19 +02:00
Andreas Österreicher a04d2acb86 Fixed Blank on Phrase for Abgabetool 2026-05-04 10:49:33 +02:00
Harald Bamberger de2aabf00b readd dokument preview link to api response 2026-05-04 09:30:16 +02:00
ma0068 685fc69e5d update css and add provisional height 2026-04-30 18:02:38 +02:00
Werner Masik 58a921b500 changed lohnguide kommentar data type to text 2026-04-30 09:47:12 +02:00
Harald Bamberger af8814468f Merge branch 'feature-76669/optimizing-menu-load' into cis40_2026-02_rc 2026-04-30 07:43:56 +02:00
Harald Bamberger c8a6e2f7cd Merge branch 'master' into cis40_2026-02_rc 2026-04-30 07:35:29 +02:00
Harald Bamberger 2227c3ecf1 Merge branch 'feature-76671/moving-avg-grade-calc-to-backend' into cis40_2026-02_rc 2026-04-30 07:33:42 +02:00
Harald Bamberger c7526bd0d9 Merge branch 'feature-76385/mobile-optimization-room-search-page' into cis40_2026-02_rc 2026-04-30 07:28:55 +02:00
Harald Bamberger 791f69b509 Merge branch 'feature-76385/mobile-optimization-profile-tabulator' into cis40_2026-02_rc 2026-04-30 07:09:56 +02:00
adisposkofh 5aded99999 code formatting 2026-04-24 15:56:39 +02:00
adisposkofh f780553773 moved avg grade calcs to backend 2026-04-24 15:55:03 +02:00
adisposkofh 868599a7fe fixed profile tables headings localization 2026-04-24 10:50:32 +02:00
Johann Hoffmann e86e7f0bd8 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; 2026-04-23 15:59:38 +02:00
adisposkofh 331381c94d removed obsolete comments 2026-04-23 15:05:54 +02:00
adisposkofh 478b23825c fixed broken formatting in collapsing profile tabulators 2026-04-23 15:05:21 +02:00
Johann Hoffmann c36f259571 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?; 2026-04-21 17:40:24 +02:00
Johann Hoffmann 501bae585a WIP 2026-04-21 16:59:10 +02:00
Johann Hoffmann 2c72f704d0 remove unnecessary bottom calc functions 2026-04-21 15:32:51 +02:00
Johann Hoffmann 194de7b4e7 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 2026-04-21 15:29:45 +02:00
Johann Hoffmann c03609142b 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; 2026-04-21 14:05:19 +02:00
adisposkofh 956b201757 minor fix 2026-04-21 11:25:48 +02:00
Johann Hoffmann 5374f71732 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 2026-04-20 17:54:27 +02:00
adisposkofh e5015f348b reworked roomsearch inputs spacing 2026-04-20 17:07:49 +02:00
adisposkofh 6792002c19 added spacing between room search inputs, mostly visible in mobile 2026-04-20 17:03:05 +02:00
adisposkofh 9890f6aade fixed issue: context menu appearing on vue datepicker single click in mobile 2026-04-20 16:58:43 +02:00
Harald Bamberger c53d451000 Merge branch 'master' into feature-68301/cis4_ma_raumreservierung 2026-04-20 16:34:30 +02:00
Harald Bamberger f6747713a1 Merge branch 'master' into feature-60873/GesamtnoteneingabeCis4 2026-04-20 11:13:14 +02:00
Harald Bamberger 59ddf175ed Merge branch 'master' into feature-61236/Cis4MenuErweiterung 2026-04-20 10:47:58 +02:00
Johann Hoffmann ccd8d5f871 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 2026-04-20 10:28:49 +02:00
Harald Bamberger bd47ad2b8c Merge branch 'master' into feature-69187/CIS_MA_Pruefungsprotokolle 2026-04-20 09:48:34 +02:00
ma0068 26db4a5e7a adding redraw tabulator and fallback to avoid empty column 2026-04-20 09:31:52 +02:00
Harald Bamberger e90019cdd8 Merge branch 'master' into feature-68610/CIS4_Projektabgabe 2026-04-20 09:30:54 +02:00
Johann Hoffmann be508c99ea fetch & show lva semesterstunden in cards template in footer area 2026-04-16 11:59:10 +02:00
ma0048 21d80905a2 akzeptierte dokumente anzeigen, auch wenn kein dokument vorhanden ist 2026-04-13 13:04:46 +02:00
Johann Hoffmann 71a77fc576 dont render lv.js menuitem dropdowntoggle if the item is marked as 'unavailable', even if it has links attached 2026-04-08 16:10:08 +02:00
Johann Hoffmann a028297da6 call authInfo api on created in fhc app to provide isStudent, isMitarbeiter & uid; renamed main component of MyLv route from 'student' to 'MyLv' since it fits for employees aswell; adapt Lv.js component to render a bootstrap5 dropdown list when menuItem has a c4_linkList or c4_moodle_links; 2026-04-08 15:11:03 +02:00
Johann Hoffmann ac44b36b59 cis4 myLv employee functionality implemented; wrote new query fetching studiensemester an employee in which he had assigned lehraufträge; afterwards fetch all lva by employee uid and sem_kurzbz so they fit in the existing component structure; MyLv/Lvs/SS2026 etc now also returns a 'student' | 'employee' string based on path taken inside the controller to aid vue components in rendering useful information and avoiding senseless fetch requests -> e.g. employees cant have grades on lva they are teaching; added component names for sanity when using vuedevtools; 2026-04-07 16:07:10 +02:00
ma0068 298dbbf400 use context vm for tabulator event, refactor function toggleRowClick, delete unused commented sections 2026-04-02 10:58:49 +02:00
Johann Hoffmann 5a6d20f817 fixed scrollOffset discrepancy issue by UNDEFINING rowHeight and thus let tabulator detect the actual size of a row itself and not trust a sligthly wrong rowHeight value; define centered formatter in js/tabulator/formatter/centered and use it on unformatted columns for consistency; 2026-03-05 15:47:19 +01:00
Johann Hoffmann ce5da22180 import error msg phrase; config for noten which dount count towards prüfungsantritt & load & use that; 2026-03-03 16:47:17 +01:00
Johann Hoffmann 05b2c3c42b persistence of order/width/visibility of notenTable predefined columns before applying dynamic pruefung columns in setupData and other table col manipulating routines; fix antrittCalculation reactivity and resulting selection eligibility and the reactivity of that also; added custom sorter for pruefung cols; some minor UX tweaks; 2026-03-03 12:05:05 +01:00
Johann Hoffmann 2dd732b924 Merge branch 'master' into feature-60873/GesamtnoteneingabeCis4
# Conflicts:
#	application/views/CisRouterView/CisRouterView.php
2026-02-27 10:38:24 +01:00
Johann Hoffmann 110f73e622 WIP notentool table persistence with dyn cols 2026-02-17 14:09:57 +01:00
Johann Hoffmann bb0d118284 Pruefung Termin1/original Note column fixed logic, variable column creation reserved for termin2/termin3 mixtures; remove redundant termintypen from student row in table if backend sent a succesful update response since their cant be multiple termin2/termin3; block notenvorschlag übernehmen once a pruefung exists; 2026-02-13 12:25:55 +01:00
Johann Hoffmann 701ccadff3 approveGrades phrase with counter; computed maxAntritte by config settings; block illegal pruefung creation in frontend (backend was already working); 2026-02-12 13:56:56 +01:00
Johann Hoffmann 34555504df Notenfreigabe email template insert in dbupdate3.4; notenvorschlag übernehmen cellHandler adjustment; WIP testing pruefungen & pruefungs config 2026-02-11 10:20:53 +01:00
Johann Hoffmann d1f5220925 notenschluessel availability check; WIP lvgesamtnote model method that actually fetches the notenvorschlag without join over zeugnisnote; 2026-02-10 12:57:38 +01:00
Johann Hoffmann decd514b22 WIP improving notenimport with punktefeature 2026-02-09 09:50:22 +01:00
Johann Hoffmann 6cf7093293 testing/implementing more config flags; dont select on certain cols; certain cols only available with certain flags; 2026-02-05 16:49:26 +01:00
Johann Hoffmann 17f11fa871 Merge remote-tracking branch 'origin/master' into feature-60873/GesamtnoteneingabeCis4
# Conflicts:
#	application/controllers/api/frontend/v1/Lehre.php
#	application/models/education/Lehrveranstaltung_model.php
#	application/models/education/Note_model.php
#	application/views/CisRouterView/CisRouterView.php
#	public/js/api/factory/studiensemester.js
#	public/js/components/Bootstrap/Offcanvas.js
#	public/js/components/Overlay/FhcOverlay.js
2026-02-04 15:40:34 +01:00
Johann Hoffmann 96812868a4 WIP 2026-02-04 10:31:20 +01:00
Johann Hoffmann 40c79158f7 punkte feature basically finished; WIP testing & import rewrite 2026-02-03 17:38:33 +01:00
Johann Hoffmann 054663ee00 WIP punkte 2026-02-03 11:34:59 +01:00
Johann Hoffmann 81eee814e9 yellow dropdown styling only on editable tabulator colums for note_vorschlag; fetch note for punkte for notenvorschlag and pruefungsnote if certain config is set; added debounce helper file/function; WIP persisting punkte in backend 2026-02-02 17:07:16 +01:00
Johann Hoffmann 390a3c0d5a Notenschluessel Model + WIP making sense of legacy config flags 2026-01-29 14:50:37 +01:00
Johann Hoffmann 695dd655c0 WIP implementing getNotenvorschlagStudent, currently only works for whole lva/sem 2026-01-26 14:47:21 +01:00
ma0068 c34ffedb42 Autocomplete Field Vertretung
Form with ISO fields for hours
Admin functionality for seeing Timelocks of uid in route
Backend with CI-Validations
Phrases
2026-01-21 10:59:16 +01:00
ma0068 b5382b1bdf Form and Start Backend 2026-01-15 15:39:42 +01:00
ma0068 519cbc7601 base structure and table 2026-01-14 09:26:15 +01:00
Johann Hoffmann 957da460a6 shorter passwort freigabe text; loadCisConfig for Benotungstool via api; anw% in notentable via event; WIP incorporating CIS config into actual noten logic; 2025-12-18 15:24:08 +01:00
ma0048 8f9f447acf cis4 raumreservierung beta version 2025-12-18 11:08:40 +01:00
Johann Hoffmann 2cee36d7b5 try/catch around moodle event & proper error message 2025-12-17 14:29:05 +01:00
Johann Hoffmann 43925e3088 custom sticky css; offcanvas mobility legende; mobiltiy zusatz in seperate column; added getMobilityZusatzForUids & formatZusatz similar to digital anw mobility zusaetze; 2025-11-25 17:23:49 +01:00
Johann Hoffmann 1c236cce02 Merge branch 'master' into feature-60873/GesamtnoteneingabeCis4
# Conflicts:
#	application/config/routes.php
#	application/models/crm/Prestudent_model.php
#	application/models/education/Lehreinheit_model.php
#	application/models/education/Lehrveranstaltung_model.php
#	public/js/apps/Dashboard/Fhc.js
#	system/phrasesupdate.php
2025-11-25 10:50:26 +01:00
Johann Hoffmann 4956a517ca loading overlay, notenfreigabe available phrase with counter; placeholder phrasen & date format; WIP handling moodle API errors well 2025-11-25 10:31:52 +01:00
Alexei Karpenko d9c7df736c Pruefungsprotokolle: added cis header/footer switch, added language in form 2025-11-14 11:01:11 +01:00
Alexei Karpenko 73244df019 Projektabgaben Uebersicht: added comments, changed placehoder search text, added phrase 2025-11-11 12:41:20 +01:00
Alexei Karpenko 91d24ebae8 Projektabgabe Übersicht: added phrases 2025-11-10 17:37:24 +01:00
Alexei Karpenko 6861e26ed6 Projektabgabe Übersicht: added flag "inVisualLibrary with event" 2025-11-10 14:43:28 +01:00
Alexei Karpenko 34242e12ea Projektabgabe Uebersicht: added person status 2025-11-07 02:22:21 +01:00
Alexei Karpenko bd67e41aa6 Projektabgabe Uebersicht: added zip download, bugfixes Projektabgabe search 2025-11-05 11:44:36 +01:00
Alexei Karpenko 601c6c53e7 CIS4: added Projektabgabe Uebersicht, enabled filtering by Abgabe data and person data 2025-11-03 15:49:31 +01:00
Alexei Karpenko b7ba740a3a Merge branch 'master' into feature-68610/CIS4_Projektabgabe 2025-11-03 15:47:31 +01:00
Johann Hoffmann a6167583a3 hinweistexte import/freigabe; distinct css for editable table cols notenvorschlag & freigabe; trigger 'getEntschuldigungsStatusForStudentOnDate' event when saving a pruefungstermin -> if akzeptierte entschuldigung is found for student on pruefungsdate it is automatically set to entschuldigt; fix event unmount lifecycle; 2025-10-07 16:26:58 +02:00
ma0048 ba6224bc78 oeh betrag aus der eigener tabelle holen
studentenverwaltung bei jedem studiensemester wechsel
fas nur einmalig ueber die variable
2025-09-02 11:18:24 +02:00
Johann Hoffmann 1e68eb0b90 berechtigungsprüfung 'lehre/benotungstool:rw' in Noten Controller; API Method documentation; removed addMeta statements; removed Tabulator Event logging; 2025-08-21 13:35:38 +02:00
Johann Hoffmann 332efd4106 selection fixes; phrases; preserve scroll after redraw when pressing action buttons on far right; take nav offset into account for width calculation; 2025-08-20 16:25:21 +02:00
Johann Hoffmann f303191c54 alternative email per cis global config 2025-08-19 16:33:12 +02:00
Johann Hoffmann d6c7f16ceb Merge remote-tracking branch 'origin/master' into feature-60873/GesamtnoteneingabeCis4 2025-08-19 13:43:23 +02:00
Johann Hoffmann f1912fe739 custom selection handling due to bugged tab5 rowSelect, should work as intended now, WIP illegal emails 2025-08-19 13:32:18 +02:00
Johann Hoffmann 2f7fe05d21 mobility legende; TopCalc Row (sum, negative, prueflinge); fix row selection issues; 2025-08-18 11:25:38 +02:00
Johann Hoffmann ee4b61f549 recommit branch; event naming changes; 2025-08-18 09:13:14 +02:00
Johann Hoffmann 511a4256bc moved LE loading and infoString setup into LehreinheitenModule, which can be bound to an instance of Primevue3 Dropdown via v-bind. WIP modularizing other common selections like LVA & Semester Kurzbz; 2025-08-08 13:35:01 +02:00
Johann Hoffmann 3c9db86df2 Merge remote-tracking branch 'origin/master' into feature-60873/GesamtnoteneingabeCis4
# Conflicts:
#	application/config/routes.php
#	application/controllers/api/frontend/v1/Lehre.php
#	application/models/education/Lehrveranstaltung_model.php
#	application/views/CisRouterView/CisRouterView.php
#	public/js/apps/Dashboard/Fhc.js
#	system/phrasesupdate.php
2025-08-07 15:17:10 +02:00
Johann Hoffmann 367204a1ee removed legacy classes (except mobility) and moved crud functionality to LePruefungModel, LVgesamtnoteModel & LehrveranstaltungModel; 2025-08-07 14:54:41 +02:00
Johann Hoffmann bbe55a75ea noten/pruefungen import; import validation for nr of antritte and date/antritt chronological order; 2025-08-04 14:27:33 +02:00
Johann Hoffmann e58bf3a8cf WIP noten/pruefung import 2025-08-01 09:48:46 +02:00
Johann Hoffmann 1f2f866c61 positiv/negativ/unbenotet filter; reload lva/le correctly on sem/lva dropdown selection change; 2025-07-30 17:10:43 +02:00
Johann Hoffmann 6ccbc95697 createPruefung entry with "noch nicht eingetragen" note as default for a selection of students at given date; filterHeaders in noten cols; antrittCount col; pruefungsformatter with antritt highlighting; 2025-07-29 17:32:07 +02:00
Johann Hoffmann 52d9e0a195 pruefungen columns pro datum nicht pro termintyp; row Selection & modal newPruefungForSelectedStudents mit linked multiselect dropdown; WIP antritte berechnen 2025-07-25 12:50:51 +02:00
Johann Hoffmann 6a3982347b teilnoten/punkte berechnen für notenvorschläge; vorschläge übernehmen; noten freigabe mit passwort; prüfungen generisch anzeigen nach datum gruppiert; prüfungen anlegen/bearbeiten auf mapping termin1/2/3 möglich. 2025-07-24 17:02:57 +02:00
Johann Hoffmann fe7feeb74e Merge remote-tracking branch 'origin/master' into feature-60873/GesamtnoteneingabeCis4 2025-07-15 09:51:11 +02:00
SimonGschnell b2419beca6 refactor(CIS4 MitarbeiterSeiten integration): updates the approveLehrauftrag view to include the CIS4 header and footer 2025-06-04 15:02:33 +02:00
SimonGschnell b752b475d9 update(Lehrauftrag Mitarbeiter View): adds the new CIS4-Header/Footer if in CIS4 context and updates minor layout problems 2025-06-04 11:18:02 +02:00
Johann Hoffmann 658fe79ad7 load Teilnoten via moodle Event trigger; WIP further noten/punkte logic; 2025-05-12 16:45:33 +02:00
Johann Hoffmann 5bbf05ac8a WIP Gesamtnoteneingabe Notenberechnung endpoint 2025-05-12 12:50:56 +02:00
131 changed files with 11630 additions and 2003 deletions
+9
View File
@@ -23,6 +23,15 @@ Events::on('loadRenderers', function ($renderers) {
);
});
Events::on('loadRenderers', function ($renderers) {
$fhc_core_renderers =& $renderers();
$fhc_core_renderers["slot_room"] = array(
'modalTitle' => APP_ROOT.'public/js/components/Cis/Renderer/Slot/roomModalTitle.js',
'modalContent' => APP_ROOT.'public/js/components/Cis/Renderer/Slot/roomModalContent.js',
'calendarEventStyles' => APP_ROOT.'public/css/Cis4/CoreCalendarEvents.css'
);
});
Events::on('loadRenderers', function ($renderers) {
$fhc_core_renderers =& $renderers();
$fhc_core_renderers["ferien"] = array(
+6
View File
@@ -0,0 +1,6 @@
<?php
if (!defined('BASEPATH')) exit('No direct script access allowed');
// 'entschuldigt' & 'noch nicht eingetragen' -> wirken sich nicht auf Antritte aus
$config['NOTEN_OHNE_ANTRITT'] = [9, 17]; // tbl_note pk
+1
View File
@@ -67,6 +67,7 @@ $route['Cis/MyLv/.*'] = 'Cis/MyLv/index/$1';
$route['Cis/OtherLvPlan/.*'] = 'Cis/OtherLvPlan/index/$1';
//Route for LV Plan Stg/Semester/Verband/Gruppe
$route['Cis/StgOrgLvPlan/.*'] = 'Cis/StgOrgLvPlan/index/$1';
$route['Cis/Benotungstool/.*'] = 'Cis/Benotungstool/index/$1';
$route['Abgabetool/Assistenz'] = 'Cis/Abgabetool/Assistenz';
$route['Abgabetool/Assistenz/(:any)'] = 'Cis/Abgabetool/Assistenz/$1';
+2 -1
View File
@@ -22,8 +22,9 @@ unset($config['student']['searchfields']['email']);
unset($config['student']['searchfields']['tel']);
$config['student']['resultfields'] = [
"s.student_uid AS uid",
"s.matrikelnr",
"s.matrikelnr AS personenkennzeichen",
"p.person_id",
"p.matr_nr AS matrikelnummer",
"(p.vorname || ' ' || p.nachname) AS name",
"ARRAY[s.student_uid || '@' || '" . DOMAIN . "'] AS email",
"CASE
@@ -0,0 +1,37 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
/**
*
*/
class Benotungstool extends Auth_Controller
{
/**
* Constructor
*/
public function __construct()
{
parent::__construct([
'index' => self::PERM_LOGGED
]);
$this->_ci =& get_instance();
}
// -----------------------------------------------------------------------------------------------------------------
// Public methods
/**
* @return void
*/
public function index()
{
$viewData = array(
'uid'=>getAuthUID(),
);
$this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'Benotungstool']);
}
}
@@ -0,0 +1,30 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
/**
*
*/
class ProjektabgabeUebersicht extends Auth_Controller
{
/**
* Constructor
*/
public function __construct()
{
parent::__construct([
'index' => ['basis/cis:r']
]);
}
// -----------------------------------------------------------------------------------------------------------------
// Public methods
/**
* @return void
*/
public function index()
{
$this->load->view('CisRouterView/CisRouterView.php', ['route' => 'ProjektabgabeUebersicht']);
}
}
@@ -0,0 +1,30 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
class Zeitsperren extends Auth_Controller
{
public function __construct()
{
parent::__construct([
'index' => ['basis/cis:r'],
]);
// Load Libraries
$this->load->library('VariableLib', ['uid' => getAuthUID()]);
}
/**
* index loads the view Zeitsperren
* @access public
* @return void
*/
public function index()
{
$viewData = array(
'uid'=>getAuthUID(),
);
$this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'zeitsperren']);
}
}
+124 -29
View File
@@ -18,18 +18,9 @@
if (! defined('BASEPATH')) exit('No direct script access allowed');
//require_once('../../../include/studiengang.class.php');
//require_once('../../../include/student.class.php');
//require_once('../../../include/datum.class.php');
//require_once('../../../include/mail.class.php');
//require_once('../../../include/benutzerberechtigung.class.php');
//require_once('../../../include/phrasen.class.php');
//require_once('../../../include/projektarbeit.class.php');
//require_once('../../../include/projektbetreuer.class.php');
class Lehre extends FHCAPI_Controller
{
/**
* Object initialization
*/
@@ -38,39 +29,58 @@ class Lehre extends FHCAPI_Controller
parent::__construct([
'lvStudentenMail' => self::PERM_LOGGED,
'LV' => self::PERM_LOGGED,
'Pruefungen' => self::PERM_LOGGED
'Pruefungen' => self::PERM_LOGGED,
'semesterAverageGrade' => self::PERM_LOGGED,
'getZugewieseneLv' => self::PERM_LOGGED,
'getLeForLv' => self::PERM_LOGGED
]);
$this->load->library('PhrasesLib');
$this->loadPhrases(
array(
'global',
'ui',
'abgabetool'
)
);
$this->load->helper('hlp_sancho_helper');
require_once(FHCPATH . 'include/studiengang.class.php');
require_once(FHCPATH . 'include/student.class.php');
require_once(FHCPATH . 'include/projektarbeit.class.php');
require_once(FHCPATH . 'include/projektbetreuer.class.php');
}
//------------------------------------------------------------------------------------------------------------------
// Public methods
/**
/**
* constructs the emails of the groups from a lehrveranstaltung
*/
public function lvStudentenMail()
public function lvStudentenMail()
{
$lehreinheit_id = $this->input->get("lehreinheit_id",TRUE);
// return early if the required parameter is missing
if(!isset($lehreinheit_id))
{
$this->terminateWithError('Missing required parameter', self::ERROR_TYPE_GENERAL);
}
$lehreinheit_id = $this->input->get("lehreinheit_id",TRUE);
$this->load->model('education/Lehreinheit_model', 'LehreinheitModel');
$studentenMails = $this->LehreinheitModel->getStudentenMail($lehreinheit_id);
// return early if the required parameter is missing
if(!isset($lehreinheit_id))
{
$this->terminateWithError('Missing required parameter', self::ERROR_TYPE_GENERAL);
}
$studentenMails = $this->getDataOrTerminateWithError($studentenMails);
$this->load->model('education/Lehreinheit_model', 'LehreinheitModel');
$studentenMails = $this->LehreinheitModel->getStudentenMail($lehreinheit_id);
$studentenMails = $this->getDataOrTerminateWithError($studentenMails);
//convert array of objects into array of strings
$studentenMails = array_map(function($element){
return $element->mail;
}, $studentenMails);
$this->terminateWithSuccess($studentenMails);
$this->terminateWithSuccess($studentenMails);
}
public function LV($studiensemester_kurzbz, $lehrveranstaltung_id)
@@ -80,13 +90,13 @@ class Lehre extends FHCAPI_Controller
$result = $this->LehrveranstaltungModel->getLvsByStudentWithGrades(getAuthUID(), $studiensemester_kurzbz, getUserLanguage(), $lehrveranstaltung_id);
$result = current($this->getDataOrTerminateWithError($result));
$this->terminateWithSuccess($result);
}
/**
* fetches all Pruefungen of a student for a specific lehrveranstaltung
* if the student passed the Pruefung on the first attempt, no information about the Pruefungen is stored in the database
* if the student passed the Pruefung on the first attempt, no information about the Pruefungen is stored in the database
* @param mixed $lehrveranstaltung_id
* @return void
*/
@@ -100,5 +110,90 @@ class Lehre extends FHCAPI_Controller
$this->terminateWithSuccess($result);
}
}
/**
* calculates and returns the grade average and weighted average for a specific semester
* @param string $studiensemester_kurzbz
* @return void
*/
public function semesterAverageGrade($studiensemester_kurzbz)
{
$this->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel');
$semesterLvs = $this->LehrveranstaltungModel->getLvsByStudentWithGrades(getAuthUID(), $studiensemester_kurzbz, getUserLanguage());
if (isError($semesterLvs))
return $this->outputJsonError(getError($semesterLvs));
$semesterLvsData = getData($semesterLvs);
$doGradesExist = false;
$sum = 0;
$count = 0;
$sumWeighted = 0;
$sumEcts = 0;
foreach ($semesterLvsData as $lv) {
if (!$lv->znote || $lv->znote < 1 || $lv->znote > 5)
continue;
$doGradesExist = true;
$sum += $lv->znote;
$count++;
$sumWeighted += $lv->znote * floatval($lv->ects);
$sumEcts += floatval($lv->ects);
}
$averageGrade = null;
$weightedAverageGrade = null;
if ($doGradesExist) {
$averageGrade = $sum/$count;
$weightedAverageGrade = $sumWeighted/$sumEcts;
}
$this->terminateWithSuccess(['average_grade' => $averageGrade, 'weighted_average_grade' => $weightedAverageGrade]);
}
/**
* fetches all assigned lehrveranstaltungen of a mitarbeiter for a given semester
* @param mixed $uid
* @param mixed $sem_kurzbz
* @return void
*/
public function getZugewieseneLv() {
$uid = $this->input->get("uid",TRUE);
$sem_kurzbz = $this->input->get("sem_kurzbz",TRUE);
// TODO: error messages
if(!isset($sem_kurzbz) || isEmptyString($sem_kurzbz))
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
if (!isset($uid) || isEmptyString($uid))
$uid = getAuthUID();
// querying other ma_uids data requires admin permission
if($uid !== getAuthUID()) {
$this->load->library('PermissionLib');
$isAdmin = $this->permissionlib->isBerechtigt('admin');
if(!$isAdmin) $this->terminateWithError($this->p->t('ui', 'keineBerechtigung'), 'general');
}
$this->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel');
$result = $this->LehrveranstaltungModel->getLvForLektorInSemester($sem_kurzbz, $uid);
$data = $this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess($data);
}
public function getLeForLv() {
$lv_id = $this->input->get("lv_id",TRUE);
$sem_kurzbz = $this->input->get("sem_kurzbz",TRUE);
$this->load->model('education/Lehreinheit_model', 'LehreinheitModel');
// $this->terminateWithSuccess($this->LehreinheitModel->getLesForLv($lv_id, $sem_kurzbz));
$this->terminateWithSuccess($this->LehreinheitModel->getAllLehreinheitenForLvaAndMaUid($lv_id, getAuthUID(), $sem_kurzbz));
}
}
@@ -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");
@@ -61,24 +62,23 @@ 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){
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
}
$lvMenu = $this->getLvMenuInternal($lvMenuOptions['lvid'],$lvMenuOptions['studiensemester_kurzbz']);
$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 +89,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 +105,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 +124,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 +139,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 +148,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 +211,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 +224,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 +272,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 +291,7 @@ class LvMenu extends FHCAPI_Controller
unset($menu[$key]);
continue;
}
// fills pos array to sort the menu
$pos[$key] = $row['position'];
@@ -299,11 +299,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){
@@ -99,7 +99,7 @@ class LvPlan extends FHCAPI_Controller
$end_date = $this->input->post('end_date', true);
$uid = $this->input->post('uid', true);
// disallow accessing other user's lv plan if missing permission
// disallow accessing other user's events if missing permission
if ($uid && $uid !== getAuthUID() && !$this->permissionlib->isBerechtigt('basis/other_lv_plan')) {
$this->terminateWithError("Missing permission to view other users' timetables!");
}
@@ -109,7 +109,7 @@ class LvPlan extends FHCAPI_Controller
$lvplanEvents = $this->getDataOrTerminateWithError($result);
// fetching moodle events
$moodleEvents = $uid ? [] : $this->fetchMoodleEvents($start_date, $end_date);
$moodleEvents = $this->fetchMoodleEvents($start_date, $end_date, $uid);
// fetching ferien events
$ferienEvents = $this->fetchFerienEvents($start_date, $end_date, $uid);
@@ -288,6 +288,11 @@ class LvPlan extends FHCAPI_Controller
$end_date = $this->input->post('end_date', true);
$uid = $this->input->post('uid', true);
// disallow accessing other user's reservierungen if missing permission
if ($uid && $uid !== getAuthUID() && !$this->permissionlib->isBerechtigt('basis/other_lv_plan')) {
$this->terminateWithError("Missing permission to view other users' timetables!");
}
// get data
$this->load->library('StundenplanLib');
@@ -401,7 +406,7 @@ class LvPlan extends FHCAPI_Controller
*/
public function compactibleEventTypes()
{
$this->terminateWithSuccess(["lehreinheit", "reservierung"]);
$this->terminateWithSuccess(["lehreinheit", "reservierung", "ferien", "moodle"]);
}
/**
@@ -411,7 +416,7 @@ class LvPlan extends FHCAPI_Controller
* @param string $end_date
* @return array
*/
private function fetchMoodleEvents($start_date, $end_date)
private function fetchMoodleEvents($start_date, $end_date, $uid = null)
{
$this->load->config('calendar');
@@ -434,7 +439,7 @@ class LvPlan extends FHCAPI_Controller
[
'start_date' => $start->format('c'),
'end_date' => $end->format('c'),
'username' => getAuthUID()
'username' => $uid ?? getAuthUID()
]
);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,67 @@
<?php
/**
* Copyright (C) 2024 fhcomplete.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
if (! defined('BASEPATH')) exit('No direct script access allowed');
class Studiensemester extends FHCAPI_Controller
{
private $_ci;
/**
* Object initialization
*/
public function __construct()
{
parent::__construct([
'getStudiensemester'=> self::PERM_LOGGED,
]);
$this->_ci =& get_instance();
$this->_ci->load->model('organisation/Studiensemester_model', 'StudiensemesterModel');
}
//------------------------------------------------------------------------------------------------------------------
// Public methods
/**
* GET METHOD
* returns List of all studiensemester as well as current one
*/
public function getStudiensemester()
{
$this->_ci->StudiensemesterModel->addOrder("start", "DESC");
$result = $this->_ci->StudiensemesterModel->load();
$studiensemester = getData($result);
$result = $this->_ci->StudiensemesterModel->getAkt();
$aktuell = getData($result);
$this->terminateWithSuccess(array($studiensemester, $aktuell));
}
//------------------------------------------------------------------------------------------------------------------
// Private methods
}
@@ -0,0 +1,367 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
class Zeitsperren extends FHCAPI_Controller
{
public function __construct()
{
parent::__construct([
'getZeitsperrenUser' => self::PERM_LOGGED,
'getTypenZeitsperren' => self::PERM_LOGGED,
'getTypenErreichbarkeit' => self::PERM_LOGGED,
'getStunden' => self::PERM_LOGGED,
'loadZeitsperre' => self::PERM_LOGGED,
'add' => self::PERM_LOGGED,
'update' => self::PERM_LOGGED,
'delete' => self::PERM_LOGGED,
]);
// Load Libraries
$this->load->library('VariableLib', ['uid' => getAuthUID()]);
$this->load->library('form_validation');
// Load language phrases
$this->loadPhrases([
'ui',
'person',
'zeitsperren'
]);
// Load models
$this->load->model('ressource/Zeitsperre_model', 'ZeitsperreModel');
$this->load->model('ressource/Zeitsperretyp_model', 'ZeitsperretypModel');
$this->load->model('ressource/Erreichbarkeit_model', 'ErreichbarkeitModel');
$this->load->model('ressource/Stunde_model', 'StundeModel');
$this->load->model('ressource/Zeitaufzeichnung_model', 'ZeitaufzeichnungModel');
}
public function getZeitsperrenUser($uid)
{
//check if $uid is passedUser
$loggedInUser = getAuthUID();
if($loggedInUser != $uid) {
$this->load->library('PermissionLib');
$isAdmin = $this->permissionlib->isBerechtigt('admin');
if(!$isAdmin) {
$this->terminateWithError($this->p->t('ui', 'noAdmin'), self::ERROR_TYPE_GENERAL);
}
}
$result = $this->ZeitsperreModel->getZeitsperrenUser($uid);
if (isError($result)) {
$this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
}
$this->terminateWithSuccess((getData($result) ?: []));
}
public function getTypenZeitsperren()
{
$this->ZeitsperretypModel->addOrder('beschreibung', 'ASC');
$result = $this->ZeitsperretypModel->load();
if (isError($result)) {
$this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
}
$this->terminateWithSuccess((getData($result) ?: []));
}
public function getTypenErreichbarkeit()
{
$result = $this->ErreichbarkeitModel->load();
if (isError($result)) {
$this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
}
$this->terminateWithSuccess((getData($result) ?: []));
}
public function getStunden()
{
$this->StundeModel->addOrder('stunde', 'ASC');
$result = $this->StundeModel->load();
if (isError($result)) {
$this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
}
$this->terminateWithSuccess((getData($result) ?: []));
}
public function loadZeitsperre($zeitsperre_id)
{
$this->ZeitsperreModel->addSelect(
'campus.tbl_zeitsperre.*, typ.*,
ma.person_id AS ma_person_id, ma.vorname AS ma_vorname, ma.nachname AS ma_nachname,
ma.titelpre AS ma_titelpre, ma.titelpost AS ma_titelpost'
);
$this->ZeitsperreModel->addJoin('campus.tbl_zeitsperretyp typ', 'ON (typ.zeitsperretyp_kurzbz = campus.tbl_zeitsperre.zeitsperretyp_kurzbz)');
$this->ZeitsperreModel->addJoin('public.tbl_benutzer ben', 'ON (ben.uid = campus.tbl_zeitsperre.vertretung_uid)', 'LEFT');
$this->ZeitsperreModel->addJoin('public.tbl_person ma', 'ON (ma.person_id = ben.person_id)', 'LEFT');
$result = $this->ZeitsperreModel->loadWhere(
array('zeitsperre_id' => $zeitsperre_id)
);
if (isError($result)) {
$this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
}
$this->terminateWithSuccess((current(getData($result)) ?: []));
}
public function add($mitarbeiter_uid)
{
$loggedInUser = getAuthUID();
if($mitarbeiter_uid != $loggedInUser)
$this->terminateWithError($this->p->t('ui', 'noPermission'), self::ERROR_TYPE_GENERAL);
$this->form_validation->set_rules('zeitsperretyp_kurzbz', 'Grund Zeitsperre', 'required', [
'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => 'Grund Zeitsperre'])
]);
$this->form_validation->set_rules('vondatum', 'VonDatum', 'required|is_valid_date', [
'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => 'VonDatum']),
'is_valid_date' => $this->p->t('ui', 'error_notValidDate', ['field' => 'VonDatum'])
]);
$this->form_validation->set_rules('bisdatum', 'BisDatum', 'required|is_valid_date|callback_check_von_bis_datum|callback_check_diff_intval', [
'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => 'BisDatum']),
'is_valid_date' => $this->p->t('ui', 'error_notValidDate', ['field' => 'BisDatum']),
'check_von_bis_datum' => $this->p->t('zeitsperre', 'error_VonDatumGroesserAlsBisDatum'),
'check_diff_intval' => $this->p->t('zeitsperre', 'error_zeitraumAuffallendHoch')
]);
if ($this->form_validation->run() == false)
{
$this->terminateWithValidationErrors($this->form_validation->error_array());
}
$bezeichnung = $this->input->post('bezeichnung');
$vondatum = $this->input->post('vondatum');
$vonstunde = $this->input->post('vonstunde');
$bisdatum = $this->input->post('bisdatum');
$bisstunde = $this->input->post('bisstunde');
//$vonIso = $this->input->post('vonISO'); //Timestamp für Stunde
//$bisIso = $this->input->post('bisISO'); //Timestamp für Stunde
$erreichbarkeit_kurzbz = $this->input->post('erreichbarkeit_kurzbz');
$vertretung_uid = $this->input->post('vertretung_uid');
$zeitsperretyp_kurzbz = $this->input->post('zeitsperretyp_kurzbz');
//check if existing zeitsperre
$result = $this->ZeitsperreModel->getSperreByDate($mitarbeiter_uid, $vondatum, $vonstunde, true);
$data = $this->getDataOrTerminateWithError($result);
if(hasData($result))
{
$this->terminateWithError($this->p->t('zeitsperren', 'error_existingZeitsperre', ['typ'=> current($data)->zeitsperretyp_kurzbz]), self::ERROR_TYPE_GENERAL);
}
//check if existing zeitaufzeichnung
if(in_array($zeitsperretyp_kurzbz, Zeitsperre_model::BLOCKIERENDE_ZEITSPERREN))
{
$result = $this->ZeitsperreModel->existsZeitaufzeichnung($mitarbeiter_uid, $vondatum, $bisdatum);
if(hasData($result))
$this->terminateWithError($this->p->t('zeitsperren', 'error_existingZeitaufzeichnung'), self::ERROR_TYPE_GENERAL);
}
$result = $this->ZeitsperreModel->insert(
[
'mitarbeiter_uid' => $mitarbeiter_uid,
'bezeichnung' => $bezeichnung,
'vondatum' => $vondatum,
'vonstunde' => $vonstunde,
'bisdatum' => $bisdatum,
'bisstunde' => $bisstunde,
'erreichbarkeit_kurzbz' => $erreichbarkeit_kurzbz,
'zeitsperretyp_kurzbz' => $zeitsperretyp_kurzbz,
'vertretung_uid' => $vertretung_uid,
'insertvon' => $loggedInUser,
'insertamum' => date('c'),
]
);
$data = $this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess($data);
}
public function update($zeitsperre_id)
{
//check if loggedin User is owner of the zeitsperre
$loggedInUser = getAuthUID();
$result = $this->ZeitsperreModel->load($zeitsperre_id);
$data = $this->getDataOrTerminateWithError($result);
$uid = current($data)->mitarbeiter_uid;
if($uid != $loggedInUser)
$this->terminateWithError($this->p->t('ui', 'noPermission'), self::ERROR_TYPE_GENERAL);
if(!$zeitsperre_id)
{
return $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id'=> 'Zeitsperre_id']), self::ERROR_TYPE_GENERAL);
}
//get current params
$array_update = [
'bezeichnung',
'vondatum',
'vonstunde',
'bisdatum',
'bisstunde',
// 'vonISO', //Timestamp für Stunde
// 'bisISO', //Timestamp für Stunde
'erreichbarkeit_kurzbz',
'vertretung_uid',
'zeitsperretyp_kurzbz',
'mitarbeiter_uid',
];
$post = $this->input->post();
$update = [];
foreach ($array_update as $prop)
{
if (array_key_exists($prop, $post))
{
$update[$prop] = $post[$prop];
}
}
// Validation
$rulesDefined = false; //necessary, otherwise CI validation will always be triggered, even without rules
foreach ($update as $key => $val) {
switch ($key) {
case 'zeitsperretyp_kurzbz':
$this->form_validation->set_rules(
$key,
'Grund Zeitsperre',
'required',
['required' => $this->p->t('ui', 'error_fieldRequired', ['field'=>'Grund Zeitsperre'])]
);
$rulesDefined = true;
break;
case 'vondatum':
$this->form_validation->set_rules(
$key,
'VonDatum',
'required|is_valid_date',
[
'required' => $this->p->t('ui', 'error_fieldRequired', ['field'=>'VonDatum']),
'is_valid_date' => $this->p->t('ui', 'error_notValidDate', ['field'=>'VonDatum'])
]
);
$rulesDefined = true;
break;
case 'bisdatum':
$rules = 'required|is_valid_date';
if (array_key_exists('vondatum', $update)) {
$rules .= '|callback_check_von_bis_datum|callback_check_diff_intval';
}
$this->form_validation->set_rules(
$key,
'BisDatum',
$rules,
[
'required' => $this->p->t('ui', 'error_fieldRequired', ['field'=>'BisDatum']),
'is_valid_date' => $this->p->t('ui', 'error_notValidDate', ['field'=>'BisDatum']),
'check_von_bis_datum' => $this->p->t('zeitsperre', 'error_VonDatumGroesserAlsBisDatum'),
'check_diff_intval' => $this->p->t('zeitsperre', 'error_zeitraumAuffallendHoch')
]
);
$rulesDefined = true;
break;
}
}
if ($rulesDefined && $this->form_validation->run() == false) {
$this->terminateWithValidationErrors($this->form_validation->error_array());
}
if(array_key_exists('vondatum', $post) || array_key_exists('bisdatum', $post))
{
$result = $this->ZeitsperreModel->load($zeitsperre_id);
$data = $this->getDataOrTerminateWithError($result);
$data = current($data);
$mitarbeiter_uid = array_key_exists('mitarbeiter_uid', $post) ? $update['mitarbeiter_uid'] : $data->mitarbeiter_uid;
$vondatum = array_key_exists('vondatum', $post) ? $update['vondatum'] : $data->vondatum;
$bisdatum = array_key_exists('bisdatum', $post) ? $update['bisdatum'] : $data->bisdatum;
$vonstunde = array_key_exists('vonstunde', $post) ? $update['vonstunde'] : $data->vonstunde;
$zeitsperretyp_kurzbz = array_key_exists('zeitsperretyp_kurzbz', $post) ? $update['zeitsperretyp_kurzbz'] : $data->zeitsperretyp_kurzbz;
$result = $this->ZeitsperreModel->getSperreByDate($mitarbeiter_uid, $vondatum, $vonstunde, true);
$data = $this->getDataOrTerminateWithError($result);
if(hasData($result))
{
$this->terminateWithError($this->p->t('zeitsperren', 'error_existingZeitsperre', ['typ'=> current($data)->zeitsperretyp_kurzbz]), self::ERROR_TYPE_GENERAL);
}
//check if existing zeitaufzeichnung
if(in_array($zeitsperretyp_kurzbz, Zeitsperre_model::BLOCKIERENDE_ZEITSPERREN))
{
$result = $this->ZeitsperreModel->existsZeitaufzeichnung($mitarbeiter_uid, $vondatum, $bisdatum);
if(hasData($result))
$this->terminateWithError($this->p->t('zeitsperren', 'error_existingZeitaufzeichnung'), self::ERROR_TYPE_GENERAL);
}
}
if (!empty($update)) {
$update['updatevon'] = $loggedInUser;
$update['updateamum'] = date('c');
$result = $this->ZeitsperreModel->update($zeitsperre_id, $update);
$data = $this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess($data);
}
else
$this->terminateWithSuccess("no update");
}
public function delete($zeitsperre_id)
{
if (!is_numeric($zeitsperre_id) || (int)$zeitsperre_id <= 0)
{
$this->terminateWithError($this->p->t('ui', 'error_missingId', ['id' => 'Zeitsperre_id']), self::ERROR_TYPE_GENERAL);
}
//check if loggedin User is owner of the zeitsperre
$loggedInUser = getAuthUID();
$result = $this->ZeitsperreModel->load($zeitsperre_id);
$data = $this->getDataOrTerminateWithError($result);
$uid = current($data)->mitarbeiter_uid;
if($uid != $loggedInUser)
$this->terminateWithError($this->p->t('ui', 'noPermission'), self::ERROR_TYPE_GENERAL);
$result = $this->ZeitsperreModel->delete(
array('zeitsperre_id' => $zeitsperre_id)
);
if (isError($result)) {
$this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
}
$this->terminateWithSuccess((getData($result) ?: []));
}
public function check_von_bis_datum($bisdatum)
{
$vondatum = $this->input->post('vondatum');
return $vondatum <= $bisdatum;
}
public function check_diff_intval($bisdatum)
{
$vondatum = $this->input->post('vondatum');
// Intervall in days
$vonTs = strtotime($vondatum);
$bisTs = strtotime($bisdatum);
$tage = ($bisTs - $vonTs) / 86400;
// if intervall > 14
return $tage <= 14;
}
}
@@ -0,0 +1,232 @@
<?php
/**
* Copyright (C) 2024 fhcomplete.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
if (! defined('BASEPATH')) exit('No direct script access allowed');
class RoomPlan extends FHCAPI_Controller
{
/**
* Object initialization
*/
public function __construct()
{
parent::__construct([
'addRoomReservation' => self::PERM_LOGGED,
'deleteRoomReservation' => self::PERM_LOGGED,
'getRoomCreationInfo' => self::PERM_LOGGED,
'getGruppen' => self::PERM_LOGGED,
'getLektor' => self::PERM_LOGGED,
'getReservableMap' => self::PERM_LOGGED,
]);
$this->load->library('LogLib');
$this->loglib->setConfigs(array(
'classIndex' => 5,
'functionIndex' => 5,
'lineIndex' => 4,
'dbLogType' => 'API',
'dbExecuteUser' => 'RESTful API'
));
$this->load->library('form_validation');
$this->load->library('PermissionLib');
$this->load->library('StundenplanLib');
$this->loadPhrases(['ui']);
}
//------------------------------------------------------------------------------------------------------------------
// Public methods
public function addRoomReservation()
{
$this->form_validation->set_rules('selectedStart', "Start", "required");
$this->form_validation->set_rules('selectedEnd', "End", "required");
$this->form_validation->set_rules('title', "Title", "required|max_length[10]");
$this->form_validation->set_rules('beschreibung', "Beschreibung", "required|max_length[32]");
$this->form_validation->set_rules('ort_kurzbz', "Ort", "required|max_length[16]");
$this->form_validation->set_rules('studiengang', 'Studiengang', 'numeric');
$this->form_validation->set_rules('semester', 'Semester', 'integer|greater_than_equal_to[0]');
$this->form_validation->set_rules('verband', 'Verband', 'trim');
$this->form_validation->set_rules('gruppe', 'Gruppe', 'trim');
$this->form_validation->set_rules('spezialgruppe', 'Spezialgruppe', 'max_length[32]');
$this->form_validation->set_rules('lektoren', 'Lektoren');
if (!$this->form_validation->run())
$this->terminateWithValidationErrors($this->form_validation->error_array());
$start = $this->input->post('selectedStart');
$end = $this->input->post('selectedEnd');
$title = $this->input->post('title');
$beschreibung = $this->input->post('beschreibung');
$ort_kurzbz = $this->input->post('ort_kurzbz');
$studiengang_kz = $this->input->post('studiengang');
$semester = $this->input->post('semester');
$verband = $this->input->post('verband');
$gruppe = $this->input->post('gruppe');
$spezialgruppe = $this->input->post('spezialgruppe');
$lektoren = $this->input->post('lektoren');
$result = $this->stundenplanlib->addReservation($start, $end, $title, $beschreibung, $ort_kurzbz, $lektoren, $studiengang_kz, $semester, $verband, $gruppe, $spezialgruppe);
if (isError($result))
$this->terminateWithError($result);
$this->terminateWithSuccess($result);
}
public function deleteRoomReservation()
{
$reservierung_id = $this->input->post('reservierung_id');
$result = $this->stundenplanlib->deleteReservation($reservierung_id);
if (isError($result))
$this->terminateWithError($result);
$this->terminateWithSuccess($result);
}
public function getRoomCreationInfo()
{
$return_array = array('berechtigt' => false, 'studiengaenge' => []);
if (!$this->permissionlib->isBerechtigt('lehre/reservierung'))
$this->terminateWithSuccess($return_array);
$stg_berechtigungen = $this->permissionlib->getSTG_isEntitledFor('lehre/reservierung');
if (isEmptyArray($stg_berechtigungen))
$this->terminateWithSuccess($return_array);
$this->load->model('organisation/Studiengang_model', 'StudiengangModel');
$this->StudiengangModel->addSelect('studiengang_kz, UPPER(CONCAT(typ, kurzbz)) as kuerzel, kurzbzlang');
$this->StudiengangModel->addOrder('typ, kurzbz');
$this->StudiengangModel->db->where_in('studiengang_kz', $stg_berechtigungen);
$studiengaenge = $this->StudiengangModel->loadWhere(array('aktiv' => true));
if (isError($studiengaenge))
$this->terminateWithError($studiengaenge);
$return_array['studiengaenge'] = hasData($studiengaenge) ? getData($studiengaenge) : [];
$return_array['berechtigt'] = true;
$this->terminateWithSuccess($return_array);
}
public function getGruppen()
{
$query = $this->input->get('query');
if (is_null($query))
$this->terminateWithError($this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL);
$stg_berechtigungen = $this->permissionlib->getSTG_isEntitledFor('lehre/reservierung');
if (isEmptyArray($stg_berechtigungen))
$this->terminateWithSuccess([]);
$this->load->model('organisation/gruppe_model', 'GruppeModel');
$query_words = explode(' ', urldecode($query));
$this->GruppeModel->addOrder('gruppe_kurzbz');
$this->GruppeModel->db->group_start();
foreach ($query_words as $word)
{
$this->GruppeModel->db->group_start();
$this->GruppeModel->db->where('gruppe_kurzbz ILIKE', "%" . $word . "%");
$this->GruppeModel->db->or_where('bezeichnung ILIKE', "%" . $word . "%");
$this->GruppeModel->db->or_where('beschreibung ILIKE', "%" . $word . "%");
$this->GruppeModel->db->or_where('orgform_kurzbz ILIKE', "%" . $word . "%");
if (is_numeric($word))
{
$this->GruppeModel->db->or_where('studiengang_kz', $word);
}
$this->GruppeModel->db->group_end();
}
$this->GruppeModel->db->group_end();
$this->GruppeModel->db->where_in('studiengang_kz', $stg_berechtigungen);
$gruppen = $this->GruppeModel->loadWhere(array('sichtbar' => true, 'lehre' => true));
if (isError($gruppen))
$this->terminateWithError($gruppen);
$this->terminateWithSuccess(hasData($gruppen) ? getData($gruppen) : []);
}
public function getLektor()
{
$query = $this->input->get('query');
if (is_null($query))
$this->terminateWithError($this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL);
$stg_berechtigungen = $this->permissionlib->getSTG_isEntitledFor('lehre/reservierung');
if (isEmptyArray($stg_berechtigungen))
$this->terminateWithSuccess([]);
$this->load->model('ressource/Mitarbeiter_model', 'MitarbeiterModel');
$query_words = explode(' ', urldecode($query));
$this->MitarbeiterModel->addSelect('uid, person_id, vorname, nachname');
$this->MitarbeiterModel->addJoin('public.tbl_benutzer', 'uid = mitarbeiter_uid');
$this->MitarbeiterModel->addJoin('public.tbl_person', 'person_id');
$this->MitarbeiterModel->db->where('public.tbl_benutzer.aktiv', true);
$this->MitarbeiterModel->db->group_start();
foreach ($query_words as $word)
{
$this->MitarbeiterModel->db->group_start();
$this->MitarbeiterModel->db->where('tbl_person.vorname ILIKE', "%" . $word . "%");
$this->MitarbeiterModel->db->or_where('tbl_person.nachname ILIKE', "%" . $word . "%");
$this->MitarbeiterModel->db->or_where('uid ILIKE', "%" . $word . "%");
$this->MitarbeiterModel->db->group_end();
}
$this->MitarbeiterModel->db->group_end();
$this->MitarbeiterModel->addOrder('nachname');
$this->MitarbeiterModel->addOrder('vorname');
$mitarbeiter = $this->MitarbeiterModel->load();
if (isError($mitarbeiter))
$this->terminateWithError($mitarbeiter);
$this->terminateWithSuccess(hasData($mitarbeiter) ? getData($mitarbeiter) : []);
}
public function getReservableMap($ort_kurzbz = null)
{
$this->form_validation->set_rules('start_date', "StartDate", "required");
$this->form_validation->set_rules('end_date', "EndDate", "required");
if (!$this->form_validation->run())
$this->terminateWithValidationErrors($this->form_validation->error_array());
// storing the post parameter in local variables
$start_date = $this->input->post('start_date', true);
$end_date = $this->input->post('end_date', true);
$result = $this->stundenplanlib->getReservableMap($ort_kurzbz, $start_date, $end_date);
$this->terminateWithSuccess(array('reservierbarMap' => hasData($result) ? getData($result) : []));
}
}
@@ -0,0 +1,218 @@
<?php
/**
* FH-Complete
*
* @package FHC-API
* @author FHC-Team
* @copyright Copyright (c) 2016, fhcomplete.org
* @license GPLv3
* @link http://fhcomplete.org
* @since Version 1.0
* @filesource
*/
// ------------------------------------------------------------------------
if (!defined('BASEPATH')) exit('No direct script access allowed');
use CI3_Events as Events;
class PaabgabeUebersicht extends FHCAPI_Controller
{
const DOWNLOAD_PERMISSION = 'lehre/abgabetool:download';
const ABGABE_TYPES = ['Bachelor', 'Diplom'];
/**
* PaabgabeUebersicht API constructor.
*/
public function __construct()
{
parent::__construct([
'viewData' => self::PERM_LOGGED,
'getPaAbgaben' => array('lehre/abgabetool:r'),
'getStudiengaenge' => array('lehre/abgabetool:r'),
'getTermine' => array('lehre/abgabetool:r'),
'getPaAbgabetypen' => array('lehre/abgabetool:r'),
'downloadZip' => array('lehre/abgabetool:r'),
//'downloadProjektarbeit' => array('lehre/abgabetool:r')
]);
$this->load->model('education/Paabgabe_model', 'PaabgabeModel');
$this->load->library('PermissionLib');
// Load Phrases
$this->loadPhrases([
'abgabetool'
]);
}
public function viewData()
{
$viewData = [
"uid" => getAuthUID(),
// TODO create permission
"showEdit" => true,
];
$this->terminateWithSuccess($viewData);
}
/**
* Get Projektabgaben for search criteria.
*/
public function getPaAbgaben()
{
$studiengang_kz = $this->input->get('studiengang_kz');
$abgabetyp_kurzbz = $this->input->get('abgabetyp_kurzbz');
$abgabedatum = $this->input->get('abgabedatum');
$personSearchString = $this->input->get('personSearchString');
$result = $this->PaabgabeModel->getPaAbgaben(self::ABGABE_TYPES, $studiengang_kz, $abgabetyp_kurzbz, $abgabedatum, $personSearchString);
if (isError($result)) $this->terminateWithError(getError($result), self::ERROR_TYPE_DB);
// check wether Abgabe is in visual library
if (hasData($result))
{
Events::trigger('in_visual_library', getData($result));
}
$this->terminateWithSuccess(getData($result) ?: []);
}
/**
* Get all Studiengänge for which user is entitled for
*/
public function getStudiengaenge()
{
$studiengang_kz_arr = $this->permissionlib->getSTG_isEntitledFor(self::DOWNLOAD_PERMISSION);
if (!$studiengang_kz_arr) $this->terminateWithSuccess([]);
$this->load->model('organisation/Studiengang_model', 'StudiengangModel');
$this->StudiengangModel->addSelect('tbl_studiengang.*, UPPER(tbl_studiengang.typ || tbl_studiengang.kurzbz) AS kuerzel', $studiengang_kz_arr);
$this->StudiengangModel->db->where_in('studiengang_kz', $studiengang_kz_arr);
$this->StudiengangModel->addOrder('typ, kurzbz');
$result = $this->StudiengangModel->load();
if (isError($result)) $this->terminateWithError(getError($result), self::ERROR_TYPE_DB);
$this->terminateWithSuccess((getData($result) ?: []));
}
/**
* Get projekt work due dates, depending on search criteria.
*/
public function getTermine()
{
$studiengang_kz = $this->input->get('studiengang_kz');
$abgabetyp_kurzbz = $this->input->get('abgabetyp_kurzbz');
$result = $this->PaabgabeModel->getTermine(self::ABGABE_TYPES, $studiengang_kz, $abgabetyp_kurzbz);
if (isError($result)) $this->terminateWithError(getError($result), self::ERROR_TYPE_DB);
$this->terminateWithSuccess((getData($result) ?: []));
}
/**
* Get all submission types.
*/
public function getPaAbgabetypen()
{
// Load model PaabgabetypModel
$this->load->model('education/Paabgabetyp_model', 'PaabgabetypModel');
$this->PaabgabetypModel->addOrder('bezeichnung');
$result = $this->PaabgabetypModel->load();
if (isError($result)) $this->terminateWithError(getError($result), self::ERROR_TYPE_DB);
$this->terminateWithSuccess((getData($result) ?: []));
}
/**
* Download zip files with project works matching submission search criteria.
*/
public function downloadZip()
{
$studiengang_kz = $this->input->get('studiengang_kz');
$abgabetyp_kurzbz = $this->input->get('abgabetyp_kurzbz');
$abgabedatum = $this->input->get('abgabedatum');
$personSearchString = $this->input->get('personSearchString');
if (!isset($studiengang_kz) && !isset($abgabetyp_kurzbz) && !isset($abgabedatum) && !isset($personSearchString))
$this->terminateWithFileOutput('text/plain', $this->p->t('abgabetool', 'nichtsAusgewaehlt'));
$this->load->library('zip');
$result = $this->PaabgabeModel->getPaAbgaben(self::ABGABE_TYPES, $studiengang_kz, $abgabetyp_kurzbz, $abgabedatum, $personSearchString);
if (isError($result)) $this->terminateWithFileOutput('text/plain', getError($result));
$fileExists = false;
$studiengang_kuerzel = null;
if (!hasData($result)) $this->terminateWithFileOutput('text/plain', $this->p->t('abgabetool', 'keineDateienVorhanden'));
$abgaben = getData($result);
foreach ($abgaben as $abgabe)
{
$path = PAABGABE_PATH.$abgabe->paabgabe_id.'_'.$abgabe->uid.'.pdf';
if (file_exists($path))
{
$fileExists = true;
$studiengang_kuerzel = $abgabe->studiengang_kuerzel;
$this->zip->read_file($path);
}
}
if (!$fileExists) $this->terminateWithFileOutput('text/plain', $this->p->t('abgabetool', 'keineDateienVorhanden'));
$studiengang_kz = $this->input->get('studiengang_kz');
$zipFileName = 'Abgabe'.(isset($studiengang_kz) && isset($studiengang_kuerzel) ? '_'.$studiengang_kuerzel : '').'.zip';
$this->zip->download($zipFileName);
}
/**
* Download Projektarbeit document.
*/
//~ public function downloadProjektarbeit()
//~ {
//~ $paabgabe_id = $this->input->get('paabgabe_id');
//~ if (!is_numeric($paabgabe_id))
//~ $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id' => 'Abgabe ID']), self::ERROR_TYPE_GENERAL);
//~ //$abgabeRes = $this->PaabgabeModel->getEndabgabe($projektarbeit_id);
//~ $this->PaabgabeModel->addSelect("paabgabe_id, student_uid, tbl_paabgabe.datum, tbl_paabgabe.abgabedatum, projekttyp_kurzbz, titel, titel_english,
//~ paabgabe_id || '_' || student_uid || '.pdf' AS filename");
//~ $this->PaabgabeModel->addJoin('lehre.tbl_projektarbeit', 'projektarbeit_id');
//~ $abgabeRes = $this->PaabgabeModel->load($paabgabe_id);
//~ if (isError($abgabeRes))
//~ show_error(getError($abgabeRes));
//~ if (hasData($abgabeRes))
//~ {
//~ $endabgabe = getData($abgabeRes)[0];
//~ $filepath = PAABGABE_PATH.$endabgabe->filename;
//~ if (file_exists($filepath))
//~ {
//~ $this->output
//~ ->set_status_header(200)
//~ ->set_content_type('application/pdf', 'utf-8')
//~ ->set_header('Content-Disposition: attachment; filename="'.$endabgabe->filename.'"')
//~ ->set_output(file_get_contents($filepath))
//~ ->_display();
//~ }
//~ else
//~ {
//~ show_error("File does not exist.");
//~ }
//~ }
//~ }
}
@@ -78,52 +78,32 @@ class Dokumente extends FHCAPI_Controller
$this->terminateWithError($this->p->t('ui', 'errorMissingValue', ['value' => 'Studiengang_kz']), self::ERROR_TYPE_GENERAL);
$resultPreDoc = $this->_getPrestudentDokumente($prestudent_id);
$arrayAccepted = [];
$person_id = $this->_getPersonId($prestudent_id);
$docNames = array_map(function ($item) {
return $item->dokument_kurzbz;
}, $resultPreDoc);
$mergedArray = [];
foreach($docNames as $doc)
foreach ($resultPreDoc as $pre)
{
$result = $this->AkteModel->getAktenFAS($person_id, $doc, $studiengang_kz, $prestudent_id, true);
$result = $this->AkteModel->getAktenFAS($person_id, $pre->dokument_kurzbz, $studiengang_kz, $prestudent_id, true);
if (isError($result))
{
return $this->terminateWithError($result, self::ERROR_TYPE_GENERAL);
}
if (hasData($result))
{
$data = getData($result);
foreach ($data as $value)
foreach (getData($result) as $doc)
{
array_push($arrayAccepted, $value);
$merged = clone $doc;
$merged->docdatum = $pre->docdatum;
$merged->insertvonma = $pre->insertvonma;
$merged->bezeichnung = $pre->bezeichnung;
$mergedArray[] = $merged;
}
}
}
//Mapping with document_kurzbz
$preDocMap = [];
foreach ($resultPreDoc as $pre) {
$preDocMap[$pre->dokument_kurzbz] = $pre;
}
$mergedArray = [];
foreach ($arrayAccepted as $doc) {
$merged = clone $doc;
if (isset($preDocMap[$doc->dokument_kurzbz])) {
$merged->docdatum = $preDocMap[$doc->dokument_kurzbz]->docdatum;
$merged->insertvonma = $preDocMap[$doc->dokument_kurzbz]->insertvonma;
$merged->bezeichnung = $preDocMap[$doc->dokument_kurzbz]->bezeichnung;
} else {
$merged->akzeptiertdatum = null;
$merged->akzeptiertvon = null;
else
{
$mergedArray[] = $pre;
}
$mergedArray[] = $merged;
}
$this->terminateWithSuccess($mergedArray);
@@ -48,7 +48,8 @@ class Konto extends FHCAPI_Controller
// Load language phrases
$this->loadPhrases([
'konto'
'konto',
'lehre'
]);
}
@@ -112,7 +113,7 @@ class Konto extends FHCAPI_Controller
*
* @return void
*/
public function getBuchungstypen()
public function getBuchungstypen($studiensemester_kurzbz = null)
{
$this->load->model('crm/Buchungstyp_model', 'BuchungstypModel');
@@ -122,6 +123,7 @@ class Konto extends FHCAPI_Controller
$data = $this->getDataOrTerminateWithError($result);
$this->_getOEHBeitrag($data, $studiensemester_kurzbz);
$this->terminateWithSuccess($data);
}
@@ -494,4 +496,43 @@ class Konto extends FHCAPI_Controller
$this->terminateWithSuccess();
}
private function _getOEHBeitrag(&$data, $studiensemester_kurzbz = null)
{
if (is_null($studiensemester_kurzbz))
{
$this->load->library('VariableLib', ['uid' => getAuthUID()]);
$studiensemester_akt = $this->variablelib->getVar('semester_aktuell');
}
else
{
$this->load->model('organisation/Studiensemester_model', 'StudiensemesterModel');
if ($this->StudiensemesterModel->isValidStudiensemester($studiensemester_kurzbz))
$studiensemester_akt = $studiensemester_kurzbz;
else
$this->terminateWithError($this->p->t('lehre', 'error_noStudiensemester'));
}
$this->load->model('codex/Oehbeitrag_model', 'OehbeitragModel');
$oehBeitrag = $this->OehbeitragModel->getByStudiensemester($studiensemester_akt);
$oehStandardbetrag = null;
if (hasData($oehBeitrag))
{
$oeh = getData($oehBeitrag)[0];
$summe = ($oeh->studierendenbeitrag + $oeh->versicherung) * -1;
$oehStandardbetrag = number_format((float)$summe, 2, '.', '');
}
if ($oehStandardbetrag !== null)
{
$data = array_map(function ($buchungstyp) use ($oehStandardbetrag) {
if (isset($buchungstyp->buchungstyp_kurzbz) && (strtolower($buchungstyp->buchungstyp_kurzbz) === 'oeh'))
{
$buchungstyp->standardbetrag = $oehStandardbetrag;
}
return $buchungstyp;
}, $data);
}
}
}
+42 -13
View File
@@ -13,12 +13,13 @@ class Mylv extends Auth_Controller
*/
public function __construct()
{
parent::__construct([
'Student' => ['student/anrechnung_beantragen:r','user:r'], // TODO(chris): permissions?
'Studiensemester' => ['student/anrechnung_beantragen:r','user:r'], // TODO(chris): permissions?
'Lvs' => ['student/anrechnung_beantragen:r','user:r'], // TODO(chris): permissions?
'Info' => ['student/anrechnung_beantragen:r','user:r'], // TODO(chris): permissions?
'Pruefungen' => ['student/anrechnung_beantragen:r','user:r'] // TODO(chris): permissions?
'Student' => ['student/anrechnung_beantragen:r','user:r', 'basis/cis:r'], // TODO(chris): permissions?
'Studiensemester' => ['student/anrechnung_beantragen:r','user:r', 'basis/cis:r'], // TODO(chris): permissions?
'Lvs' => ['student/anrechnung_beantragen:r','user:r', 'basis/cis:r'], // TODO(chris): permissions?
'Info' => ['student/anrechnung_beantragen:r','user:r', 'basis/cis:r'], // TODO(chris): permissions?
'Pruefungen' => ['student/anrechnung_beantragen:r','user:r', 'basis/cis:r'] // TODO(chris): permissions?
]);
}
@@ -44,13 +45,27 @@ 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;
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(getData($this->StudentModel->isStudent(getAuthUID())) ?? false) { // $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));
} 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));
} else {
$this->outputJsonError('neither student or mitarbeiter');
}
}
/**
@@ -215,8 +215,11 @@ class Pruefungsprotokoll extends Auth_Controller
if (hasData($abschlusspruefung))
{
$abschlusspruefung_data = getData($abschlusspruefung);
if ($this->permissionlib->isBerechtigt('admin') ||
(isset($abschlusspruefung_data->studiengang_kz) && $this->permissionlib->isBerechtigt('assistenz', 'suid', $abschlusspruefung_data->studiengang_kz))
if ($this->permissionlib->isBerechtigt('admin')
|| (
isset($abschlusspruefung_data->studiengang_kz)
&& $this->permissionlib->isBerechtigt('assistenz', 'suid', $abschlusspruefung_data->studiengang_kz)
)
|| $this->_uid === $abschlusspruefung_data->uid_vorsitz)
$result = $abschlusspruefung;
else
+1
View File
@@ -417,6 +417,7 @@ abstract class Notiz_Controller extends FHCAPI_Controller
$notiz_id = $this->input->post('notiz_id');
$this->NotizModel->addSelect('campus.tbl_dms_version.*');
$this->NotizModel->addSelect($this->NotizModel->escape(base_url('content/notizdokdownload.php?id=')) . ' || public.tbl_notiz_dokument.dms_id AS preview');
$this->NotizModel->addJoin('public.tbl_notiz_dokument', 'ON (public.tbl_notiz_dokument.notiz_id = public.tbl_notiz.notiz_id)');
$this->NotizModel->addJoin('campus.tbl_dms_version', 'ON (public.tbl_notiz_dokument.dms_id = campus.tbl_dms_version.dms_id)');
-517
View File
@@ -1,517 +0,0 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
/*
|--------------------------------------------------------------------------
| Base Site URL
|--------------------------------------------------------------------------
|
| URL to your CodeIgniter root. Typically this will be your base URL,
| WITH a trailing slash:
|
| http://example.com/
|
| If this is not set then CodeIgniter will try guess the protocol, domain
| and path to your installation. However, you should always configure this
| explicitly and never rely on auto-guessing, especially in production
| environments.
|
*/
$config['base_url'] = 'https://c3p0.dev.technikum-wien.at/ma1434/core/FHC-Core/';
/*
|--------------------------------------------------------------------------
| Index File
|--------------------------------------------------------------------------
|
| Typically this will be your index.php file, unless you've renamed it to
| something else. If you are using mod_rewrite to remove the page set this
| variable so that it is blank.
|
*/
$config['index_page'] = 'index.ci.php';
/*
|--------------------------------------------------------------------------
| URI PROTOCOL
|--------------------------------------------------------------------------
|
| This item determines which server global should be used to retrieve the
| URI string. The default setting of 'REQUEST_URI' works for most servers.
| If your links do not seem to work, try one of the other delicious flavors:
|
| 'REQUEST_URI' Uses $_SERVER['REQUEST_URI']
| 'QUERY_STRING' Uses $_SERVER['QUERY_STRING']
| 'PATH_INFO' Uses $_SERVER['PATH_INFO']
|
| WARNING: If you set this to 'PATH_INFO', URIs will always be URL-decoded!
*/
$config['uri_protocol'] = 'REQUEST_URI';
/*
|--------------------------------------------------------------------------
| URL suffix
|--------------------------------------------------------------------------
|
| This option allows you to add a suffix to all URLs generated by CodeIgniter.
| For more information please see the user guide:
|
| http://codeigniter.com/user_guide/general/urls.html
*/
$config['url_suffix'] = '';
/*
|--------------------------------------------------------------------------
| Default Language
|--------------------------------------------------------------------------
|
| This determines which set of language files should be used. Make sure
| there is an available translation if you intend to use something other
| than english.
|
*/
$config['language'] = '';
/*
|--------------------------------------------------------------------------
| Default Character Set
|--------------------------------------------------------------------------
|
| This determines which character set is used by default in various methods
| that require a character set to be provided.
|
| See http://php.net/htmlspecialchars for a list of supported charsets.
|
*/
$config['charset'] = 'UTF-8';
/*
|--------------------------------------------------------------------------
| Enable/Disable System Hooks
|--------------------------------------------------------------------------
|
| If you would like to use the 'hooks' feature you must enable it by
| setting this variable to TRUE (boolean). See the user guide for details.
|
*/
$config['enable_hooks'] = FALSE;
/*
|--------------------------------------------------------------------------
| Class Extension Prefix
|--------------------------------------------------------------------------
|
| This item allows you to set the filename/classname prefix when extending
| native libraries. For more information please see the user guide:
|
| http://codeigniter.com/user_guide/general/core_classes.html
| http://codeigniter.com/user_guide/general/creating_libraries.html
|
*/
$config['subclass_prefix'] = 'FHC_';
/*
|--------------------------------------------------------------------------
| Composer auto-loading
|--------------------------------------------------------------------------
|
| Enabling this setting will tell CodeIgniter to look for a Composer
| package auto-loader script in application/vendor/autoload.php.
|
| $config['composer_autoload'] = TRUE;
|
| Or if you have your vendor/ directory located somewhere else, you
| can opt to set a specific path as well:
|
| $config['composer_autoload'] = '/path/to/vendor/autoload.php';
|
| For more information about Composer, please visit http://getcomposer.org/
|
| Note: This will NOT disable or override the CodeIgniter-specific
| autoloading (application/config/autoload.php)
*/
$config['composer_autoload'] = FALSE;
/*
|--------------------------------------------------------------------------
| Allowed URL Characters
|--------------------------------------------------------------------------
|
| This lets you specify which characters are permitted within your URLs.
| When someone tries to submit a URL with disallowed characters they will
| get a warning message.
|
| As a security measure you are STRONGLY encouraged to restrict URLs to
| as few characters as possible. By default only these are allowed: a-z 0-9~%.:_-
|
| Leave blank to allow all characters -- but only if you are insane.
|
| The configured value is actually a regular expression character group
| and it will be executed as: ! preg_match('/^[<permitted_uri_chars>]+$/i
|
| DO NOT CHANGE THIS UNLESS YOU FULLY UNDERSTAND THE REPERCUSSIONS!!
|
*/
$config['permitted_uri_chars'] = 'a-z 0-9~%.:_\-';
/*
|--------------------------------------------------------------------------
| Enable Query Strings
|--------------------------------------------------------------------------
|
| By default CodeIgniter uses search-engine friendly segment based URLs:
| example.com/who/what/where/
|
| By default CodeIgniter enables access to the $_GET array. If for some
| reason you would like to disable it, set 'allow_get_array' to FALSE.
|
| You can optionally enable standard query string based URLs:
| example.com?who=me&what=something&where=here
|
| Options are: TRUE or FALSE (boolean)
|
| The other items let you set the query string 'words' that will
| invoke your controllers and its functions:
| example.com/index.php?c=controller&m=function
|
| Please note that some of the helpers won't work as expected when
| this feature is enabled, since CodeIgniter is designed primarily to
| use segment based URLs.
|
*/
$config['allow_get_array'] = TRUE;
$config['enable_query_strings'] = FALSE;
$config['controller_trigger'] = 'c';
$config['function_trigger'] = 'm';
$config['directory_trigger'] = 'd';
/*
|--------------------------------------------------------------------------
| Error Logging Threshold
|--------------------------------------------------------------------------
|
| You can enable error logging by setting a threshold over zero. The
| threshold determines what gets logged. Threshold options are:
|
| 0 = Disables logging, Error logging TURNED OFF
| 1 = Error Messages (including PHP errors)
| 2 = Debug Messages
| 3 = Informational Messages
| 4 = All Messages
|
| You can also pass an array with threshold levels to show individual error types
|
| array(2) = Debug Messages, without Error Messages
|
| For a live site you'll usually only enable Errors (1) to be logged otherwise
| your log files will fill up very fast.
|
*/
$config['log_threshold'] = 1;
/*
|--------------------------------------------------------------------------
| Error Logging Directory Path
|--------------------------------------------------------------------------
|
| Leave this BLANK unless you would like to set something other than the default
| application/logs/ directory. Use a full server path with trailing slash.
|
*/
$config['log_path'] = '';
/*
|--------------------------------------------------------------------------
| Log File Extension
|--------------------------------------------------------------------------
|
| The default filename extension for log files. The default 'php' allows for
| protecting the log files via basic scripting, when they are to be stored
| under a publicly accessible directory.
|
| Note: Leaving it blank will default to 'php'.
|
*/
$config['log_file_extension'] = 'log';
/*
|--------------------------------------------------------------------------
| Log File Permissions
|--------------------------------------------------------------------------
|
| The file system permissions to be applied on newly created log files.
|
| IMPORTANT: This MUST be an integer (no quotes) and you MUST use octal
| integer notation (i.e. 0700, 0644, etc.)
*/
$config['log_file_permissions'] = 0644;
/*
|--------------------------------------------------------------------------
| Date Format for Logs
|--------------------------------------------------------------------------
|
| Each item that is logged has an associated date. You can use PHP date
| codes to set your own date formatting
|
*/
$config['log_date_format'] = 'Y-m-d H:i:s';
/*
|--------------------------------------------------------------------------
| Error Views Directory Path
|--------------------------------------------------------------------------
|
| Leave this BLANK unless you would like to set something other than the default
| application/views/errors/ directory. Use a full server path with trailing slash.
|
*/
$config['error_views_path'] = '';
/*
|--------------------------------------------------------------------------
| Cache Directory Path
|--------------------------------------------------------------------------
|
| Leave this BLANK unless you would like to set something other than the default
| application/cache/ directory. Use a full server path with trailing slash.
|
*/
$config['cache_path'] = '';
/*
|--------------------------------------------------------------------------
| Cache Include Query String
|--------------------------------------------------------------------------
|
| Whether to take the URL query string into consideration when generating
| output cache files. Valid options are:
|
| FALSE = Disabled
| TRUE = Enabled, take all query parameters into account.
| Please be aware that this may result in numerous cache
| files generated for the same page over and over again.
| array('q') = Enabled, but only take into account the specified list
| of query parameters.
|
*/
$config['cache_query_string'] = FALSE;
/*
|--------------------------------------------------------------------------
| Encryption Key
|--------------------------------------------------------------------------
|
| If you use the Encryption class, you must set an encryption key.
| See the user guide for more info.
|
| http://codeigniter.com/user_guide/libraries/encryption.html
|
*/
$config['encryption_key'] = '';
/*
|--------------------------------------------------------------------------
| Session Variables
|--------------------------------------------------------------------------
|
| 'sess_driver'
|
| The storage driver to use: files, database, redis, memcached
|
| 'sess_cookie_name'
|
| The session cookie name, must contain only [0-9a-z_-] characters
|
| 'sess_expiration'
|
| The number of SECONDS you want the session to last.
| Setting to 0 (zero) means expire when the browser is closed.
|
| 'sess_save_path'
|
| The location to save sessions to, driver dependent.
|
| For the 'files' driver, it's a path to a writable directory.
| WARNING: Only absolute paths are supported!
|
| For the 'database' driver, it's a table name.
| Please read up the manual for the format with other session drivers.
|
| IMPORTANT: You are REQUIRED to set a valid save path!
|
| 'sess_match_ip'
|
| Whether to match the user's IP address when reading the session data.
|
| 'sess_time_to_update'
|
| How many seconds between CI regenerating the session ID.
| NOTE: Keep it as it is to prevent security issues (https://en.wikipedia.org/wiki/Session_fixation)
|
| 'sess_regenerate_destroy'
|
| Whether to destroy session data associated with the old session ID
| when auto-regenerating the session ID. When set to FALSE, the data
| will be later deleted by the garbage collector.
|
| Other session cookie settings are shared with the rest of the application,
| except for 'cookie_prefix' and 'cookie_httponly', which are ignored here.
|
*/
$config['sess_driver'] = 'files';
$config['sess_cookie_name'] = 'sess_ci_session';
$config['sess_expiration'] = 1800; // Session expires every 30 minutes
$config['sess_save_path'] = NULL;
$config['sess_match_ip'] = FALSE;
$config['sess_time_to_update'] = 300;
$config['sess_regenerate_destroy'] = FALSE;
/*
|--------------------------------------------------------------------------
| Cookie Related Variables
|--------------------------------------------------------------------------
|
| 'cookie_prefix' = Set a cookie name prefix if you need to avoid collisions
| 'cookie_domain' = Set to .your-domain.com for site-wide cookies
| 'cookie_path' = Typically will be a forward slash
| 'cookie_secure' = Cookie will only be set if a secure HTTPS connection exists.
| 'cookie_httponly' = Cookie will only be accessible via HTTP(S) (no javascript)
|
| Note: These settings (with the exception of 'cookie_prefix' and
| 'cookie_httponly') will also affect sessions.
|
*/
$config['cookie_prefix'] = '';
$config['cookie_domain'] = '';
$config['cookie_path'] = '/';
$config['cookie_secure'] = FALSE;
$config['cookie_httponly'] = FALSE;
/*
|--------------------------------------------------------------------------
| Standardize newlines
|--------------------------------------------------------------------------
|
| Determines whether to standardize newline characters in input data,
| meaning to replace \r\n, \r, \n occurrences with the PHP_EOL value.
|
| This is particularly useful for portability between UNIX-based OSes,
| (usually \n) and Windows (\r\n).
|
*/
$config['standardize_newlines'] = FALSE;
/*
|--------------------------------------------------------------------------
| Global XSS Filtering
|--------------------------------------------------------------------------
|
| Determines whether the XSS filter is always active when GET, POST or
| COOKIE data is encountered
|
| WARNING: This feature is DEPRECATED and currently available only
| for backwards compatibility purposes!
|
*/
$config['global_xss_filtering'] = FALSE;
/*
|--------------------------------------------------------------------------
| Cross Site Request Forgery
|--------------------------------------------------------------------------
| Enables a CSRF cookie token to be set. When set to TRUE, token will be
| checked on a submitted form. If you are accepting user data, it is strongly
| recommended CSRF protection be enabled.
|
| 'csrf_token_name' = The token name
| 'csrf_cookie_name' = The cookie name
| 'csrf_expire' = The number in seconds the token should expire.
| 'csrf_regenerate' = Regenerate token on every submission
| 'csrf_exclude_uris' = Array of URIs which ignore CSRF checks
*/
$config['csrf_protection'] = FALSE;
$config['csrf_token_name'] = 'csrf_test_name';
$config['csrf_cookie_name'] = 'csrf_cookie_name';
$config['csrf_expire'] = 7200;
$config['csrf_regenerate'] = TRUE;
$config['csrf_exclude_uris'] = array();
/*
|--------------------------------------------------------------------------
| Output Compression
|--------------------------------------------------------------------------
|
| Enables Gzip output compression for faster page loads. When enabled,
| the output class will test whether your server supports Gzip.
| Even if it does, however, not all browsers support compression
| so enable only if you are reasonably sure your visitors can handle it.
|
| Only used if zlib.output_compression is turned off in your php.ini.
| Please do not use it together with httpd-level output compression.
|
| VERY IMPORTANT: If you are getting a blank page when compression is enabled it
| means you are prematurely outputting something to your browser. It could
| even be a line of whitespace at the end of one of your scripts. For
| compression to work, nothing can be sent before the output buffer is called
| by the output class. Do not 'echo' any values with compression enabled.
|
*/
$config['compress_output'] = FALSE;
/*
|--------------------------------------------------------------------------
| Master Time Reference
|--------------------------------------------------------------------------
|
| Options are 'local' or any PHP supported timezone. This preference tells
| the system whether to use your server's local time as the master 'now'
| reference, or convert it to the configured one timezone. See the 'date
| helper' page of the user guide for information regarding date handling.
|
*/
$config['time_reference'] = 'local';
/*
|--------------------------------------------------------------------------
| Rewrite PHP Short Tags
|--------------------------------------------------------------------------
|
| If your PHP installation does not have short tag support enabled CI
| can rewrite the tags on-the-fly, enabling you to utilize that syntax
| in your view files. Options are TRUE or FALSE (boolean)
|
| Note: You need to have eval() enabled for this to work.
|
*/
$config['rewrite_short_tags'] = FALSE;
/*
|--------------------------------------------------------------------------
| Reverse Proxy IPs
|--------------------------------------------------------------------------
|
| If your server is behind a reverse proxy, you must whitelist the proxy
| IP addresses from which CodeIgniter should trust headers such as
| HTTP_X_FORWARDED_FOR and HTTP_CLIENT_IP in order to properly identify
| the visitor's IP address.
|
| You can use both an array or a comma-separated list of proxy addresses,
| as well as specifying whole subnets. Here are a few examples:
|
| Comma-separated: '10.0.1.200,192.168.5.0/24'
| Array: array('10.0.1.200', '192.168.5.0/24')
*/
$config['proxy_ips'] = '';
/*
|--------------------------------------------------------------------------
| FHComplete Build Version
|--------------------------------------------------------------------------
|
| Version Number of the Current Build
| This is used to invalidate Cache for JS and CSS Files
|
| Example: 2019102901
*/
$config['fhcomplete_build_version'] = '2019102903';
-122
View File
@@ -1,122 +0,0 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
defined('DB_HOST') OR require_once './config/system.config.inc.php'; // For CLI-Migrations
/*
| -------------------------------------------------------------------
| DATABASE CONNECTIVITY SETTINGS
| -------------------------------------------------------------------
| This file will contain the settings needed to access your database.
|
| For complete instructions please consult the 'Database Connection'
| page of the User Guide.
|
| -------------------------------------------------------------------
| EXPLANATION OF VARIABLES
| -------------------------------------------------------------------
|
| ['dsn'] The full DSN string describe a connection to the database.
| ['hostname'] The hostname of your database server.
| ['username'] The username used to connect to the database
| ['password'] The password used to connect to the database
| ['database'] The name of the database you want to connect to
| ['dbdriver'] The database driver. e.g.: mysqli.
| Currently supported:
| cubrid, ibase, mssql, mysql, mysqli, oci8,
| odbc, pdo, postgre, sqlite, sqlite3, sqlsrv
| ['dbprefix'] You can add an optional prefix, which will be added
| to the table name when using the Query Builder class
| ['pconnect'] TRUE/FALSE - Whether to use a persistent connection
| ['db_debug'] TRUE/FALSE - Whether database errors should be displayed.
| ['cache_on'] TRUE/FALSE - Enables/disables query caching
| ['cachedir'] The path to the folder where cache files should be stored
| ['char_set'] The character set used in communicating with the database
| ['dbcollat'] The character collation used in communicating with the database
| NOTE: For MySQL and MySQLi databases, this setting is only used
| as a backup if your server is running PHP < 5.2.3 or MySQL < 5.0.7
| (and in table creation queries made with DB Forge).
| There is an incompatibility in PHP with mysql_real_escape_string() which
| can make your site vulnerable to SQL injection if you are using a
| multi-byte character set and are running versions lower than these.
| Sites using Latin-1 or UTF-8 database character set and collation are unaffected.
| ['swap_pre'] A default table prefix that should be swapped with the dbprefix
| ['encrypt'] Whether or not to use an encrypted connection.
|
| 'mysql' (deprecated), 'sqlsrv' and 'pdo/sqlsrv' drivers accept TRUE/FALSE
| 'mysqli' and 'pdo/mysql' drivers accept an array with the following options:
|
| 'ssl_key' - Path to the private key file
| 'ssl_cert' - Path to the public key certificate file
| 'ssl_ca' - Path to the certificate authority file
| 'ssl_capath' - Path to a directory containing trusted CA certificats in PEM format
| 'ssl_cipher' - List of *allowed* ciphers to be used for the encryption, separated by colons (':')
| 'ssl_verify' - TRUE/FALSE; Whether verify the server certificate or not ('mysqli' only)
|
| ['compress'] Whether or not to use client compression (MySQL only)
| ['stricton'] TRUE/FALSE - forces 'Strict Mode' connections
| - good for ensuring strict SQL while developing
| ['ssl_options'] Used to set various SSL options that can be used when making SSL connections.
| ['failover'] array - A array with 0 or more data for connections if the main should fail.
| ['save_queries'] TRUE/FALSE - Whether to "save" all executed queries.
| NOTE: Disabling this will also effectively disable both
| $this->db->last_query() and profiling of DB queries.
| When you run a query, with this setting set to TRUE (default),
| CodeIgniter will store the SQL statement for debugging purposes.
| However, this may cause high memory usage, especially if you run
| a lot of SQL queries ... disable this to avoid that problem.
|
| The $active_group variable lets you choose which connection group to
| make active. By default there is only one group (the 'default' group).
|
| The $query_builder variables lets you determine whether or not to load
| the query builder class.
*/
$active_group = 'default';
$query_builder = TRUE;
$db['default'] = array(
'dsn' => '',
'hostname' => DB_HOST,
'username' => DB_USER,
'password' => DB_PASSWORD,
'port' => DB_PORT,
'database' => DB_NAME,
'dbdriver' => 'postgre',
'dbprefix' => '',
'pconnect' => DB_CONNECT_PERSISTENT,
'db_debug' => (ENVIRONMENT !== 'production'),
'cache_on' => FALSE,
'cachedir' => '',
'char_set' => 'utf8',
'dbcollat' => 'utf8_general_ci',
'swap_pre' => '',
'encrypt' => FALSE,
'compress' => FALSE,
'stricton' => FALSE,
'failover' => array(),
'save_queries' => TRUE
);
$db['system'] = array(
'dsn' => '',
'hostname' => DB_HOST,
'username' => 'fhcomplete',
'password' => 'Fhcomplet3Onc4p1',
'database' => DB_NAME,
'port' => DB_PORT,
'dbschema' => 'public',
'dbdriver' => 'postgre',
'dbprefix' => '',
'pconnect' => DB_CONNECT_PERSISTENT,
'db_debug' => (ENVIRONMENT !== 'production'),
'cache_on' => FALSE,
'cachedir' => '',
'char_set' => 'utf8',
'dbcollat' => 'utf8_general_ci',
'swap_pre' => '',
'encrypt' => FALSE,
'compress' => FALSE,
'stricton' => FALSE,
'failover' => array(),
'save_queries' => TRUE
);
+244 -1
View File
@@ -114,6 +114,7 @@ class StundenplanLib
return $stundenplan_data;
$stundenplan_data = getData($stundenplan_data) ?? [];
$this->_ci->addMeta("stundenplanData", $stundenplan_data);
$function_error = $this->expandObjectInformation($stundenplan_data);
if ($function_error)
return $function_error;
@@ -360,7 +361,10 @@ class StundenplanLib
if (isError($ort_content_object)) {
return error(getData($ort_content_object));
}
$ort_content_object = getData($ort_content_object)[0];
$ort_content_object_data = getData($ort_content_object);
$ort_content_object = (is_array($ort_content_object_data) && count($ort_content_object_data) > 0)
? $ort_content_object_data[0]
: null;
if($ort_content_object) {
$item->ort_content_id = $ort_content_object->content_id;
}
@@ -371,6 +375,39 @@ class StundenplanLib
$item->gruppe = $gruppe_obj_array;
$item->lektor = $lektor_obj_array;
$this->_ci->load->library('PermissionLib');
$berechtigt_begrenzt = $this->_ci->permissionlib->isBerechtigt('lehre/reservierung:begrenzt', 'sui');
$now = time();
$res_lektor_start = $this->jump_day($now, RES_TAGE_LEKTOR_MIN - 1);
$res_lektor_ende = mktime(0, 0, 0, date('m', $now), date('d', $now) + RES_TAGE_LEKTOR_BIS, date('Y', $now));
$start_date = is_numeric($item->beginn) ? $item->beginn : strtotime($item->beginn);
if (!date('w', $start_date)) {
$start_date = $this->jump_day($start_date, 1);
}
$start_date_str = date('Y-m-d', $start_date);
$res_lektor_start_str = date('Y-m-d', $res_lektor_start);
$res_lektor_ende_str = date('Y-m-d', $res_lektor_ende);
$show_delete = (
(
$berechtigt_begrenzt &&
(
(isset($item->insertvon) && $item->insertvon == getAuthUID()) ||
(isset($item->uids) && in_array(getAuthUID(), $item->uids))
)
) &&
$start_date_str >= $res_lektor_start_str &&
$start_date_str <= $res_lektor_ende_str
);
if ($show_delete)
$item->deletable = true;
else
$item->deletable = false;
}
}
@@ -466,6 +503,212 @@ class StundenplanLib
return success($stundenplan_data);
}
public function addReservation($start, $end, $title, $beschreibung, $ort_kurzbz, $lektoren = null, $studiengang = null, $semester = null, $verband = null, $gruppe = null, $spezialgruppe = null)
{
$this->_ci =& get_instance();
$this->_ci->load->model('ressource/Stunde_model', 'StundeModel');
$this->_ci->load->model('ressource/Reservierung_model', 'ReservierungModel');
$this->_ci->load->model('ressource/stundenplandev_model', 'StundenplandevModel');
$this->_ci->load->model('ressource/stundenplan_model', 'StundenplanModel');
$this->_ci->load->library('PermissionLib');
$startTime = new DateTime($start);
$endTime = new DateTime($end);
$stunden = $this->_ci->StundeModel->loadWhere(array(
'beginn <' => $endTime->format('H:i:s'),
'ende >' => $startTime->format('H:i:s')
));
if (!hasData($stunden))
{
return error("Keine Stunden vorhanden");
}
$stunden = array_column(getData($stunden), 'stunde');
$this->_ci->StundenplandevModel->db->select('1');
$this->_ci->StundenplandevModel->db->where('datum', $startTime->format('Y-m-d'));
$this->_ci->StundenplandevModel->db->where('ort_kurzbz', $ort_kurzbz);
$this->_ci->StundenplandevModel->db->where_in('stunde', $stunden);
$stundenplandev_belegung = $this->_ci->StundenplandevModel->load();
$this->_ci->StundenplanModel->db->select('1');
$this->_ci->StundenplanModel->db->where('ort_kurzbz', $ort_kurzbz);
$this->_ci->StundenplanModel->db->where('datum', $startTime->format('Y-m-d'));
$this->_ci->StundenplanModel->db->where_in('stunde', $stunden);
$stundenplan_belegung = $this->_ci->StundenplanModel->load();
if ((hasData($stundenplandev_belegung) || hasData($stundenplan_belegung))
&& !$this->_ci->permissionlib->isBerechtigt('lehre/reservierungAdvanced'))
return error ('lvplan/bereitsReserviert');
$this->_ci->ReservierungModel->addSelect('stunde');
$reservation_hours = $this->_ci->ReservierungModel->loadWhere(array('datum' => $startTime->format('Y-m-d'), 'ort_kurzbz' => $ort_kurzbz));
if (isError($reservation_hours))
return $reservation_hours;
$reservation_hours = hasData($reservation_hours) ? array_column(getData($reservation_hours), 'stunde') : array();
if (!empty(array_intersect($stunden, $reservation_hours))
&& !$this->_ci->permissionlib->isBerechtigt('lehre/reservierungAdvanced'))
return error("lvplan/bereitsReserviert");
if (!empty($lektoren))
{
foreach ($lektoren as $lektor)
{
$insert = array('ort_kurzbz' => $ort_kurzbz,
'datum' => $startTime->format('Y-m-d'),
'titel' => $title,
'studiengang_kz' => is_null($studiengang) ? 0 : $studiengang,
'beschreibung' => $beschreibung,
'insertvon' => getAuthUID(),
'uid' => $lektor,
'semester' => is_null($semester) ? null : $semester,
'verband' => is_null($verband) ? null : $verband,
'gruppe' => is_null($gruppe) ? null : $gruppe,
'gruppe_kurzbz' => is_null($spezialgruppe) ? null : $spezialgruppe,
);
foreach ($stunden as $stunde)
{
$insert['stunde'] = $stunde;
$check_insert = $this->_ci->ReservierungModel->insert($insert);
if (isError($check_insert))
return $check_insert;
}
}
}
else
{
foreach ($stunden as $stunde)
{
$check_insert = $this->_ci->ReservierungModel->insert(array(
'ort_kurzbz' => $ort_kurzbz,
'uid' => getAuthUID(),
'stunde' => $stunde,
'datum' => $startTime->format('Y-m-d'),
'titel' => $title,
'studiengang_kz' => is_null($studiengang) ? 0 : $studiengang,
'beschreibung' => $beschreibung,
'insertvon' => getAuthUID()
));
if (isError($check_insert))
return $check_insert;
}
}
return success("Erfolgreich");
}
public function deleteReservation($reservierung_id)
{
$this->_ci =& get_instance();
$this->_ci->load->model('ressource/Reservierung_model', 'ReservierungModel');
$this->_ci->load->model('ressource/Stunde_model', 'StundeModel');
$this->_ci->load->library('PermissionLib');
$this->_ci->ReservierungModel->db->where_in('reservierung_id', $reservierung_id);
$reservation = $this->_ci->ReservierungModel->load();
if (isError($reservation))
return $reservation;
if (!hasData($reservation))
return error("Reservierungen nicht gefunden");
$reservations = getData($reservation);
$today = new DateTime();
foreach ($reservations as $reservierung)
{
if ($today->format('Y-m-d') > $reservierung->datum)
return error("Vergangene Reservierungen können nicht gelöscht werden");
if (($this->_ci->permissionlib->isBerechtigt('lehre/reservierung:begrenzt')) && ($reservierung->insertvon == getAuthUID() || $reservierung->uid === getAuthUID()))
{
$delete_result = $this->_ci->ReservierungModel->delete($reservierung->reservierung_id);
if (isError($delete_result))
return $delete_result;
}
}
return success("Erfolgreich");
}
public function getReservableMap($ort_kurzbz, $start_date, $end_date)
{
$this->_ci =& get_instance();
$this->_ci->load->model('ressource/Ort_model', 'OrtModel');
$this->_ci->load->library('PermissionLib');
$berechtigt_begrenzt = $this->_ci->permissionlib->isBerechtigt('lehre/reservierung:begrenzt', 'suid');
$berechtigt_erweitert = $this->_ci->permissionlib->isBerechtigt('lehre/reservierung', 'suid');
$ort_data = $this->_ci->OrtModel->load($ort_kurzbz);
if (isError($ort_data) || !hasData($ort_data))
return [];
$ort_data = getData($ort_data)[0];
if (!$ort_data->reservieren)
return [];
if (!$berechtigt_begrenzt && !$berechtigt_erweitert)
return [];
$start_ts = is_numeric($start_date) ? (int)$start_date : strtotime($start_date);
$end_ts = is_numeric($end_date) ? (int)$end_date : strtotime($end_date);
if (!$start_ts || !$end_ts)
return [];
if ($end_ts < $start_ts)
{
$tmp = $start_ts;
$start_ts = $end_ts;
$end_ts = $tmp;
}
$now = time();
$tage_min = defined('RES_TAGE_LEKTOR_MIN') ? (int)RES_TAGE_LEKTOR_MIN : 0;
$tage_bis = defined('RES_TAGE_LEKTOR_BIS') ? (int)RES_TAGE_LEKTOR_BIS : 0;
$datum_res_lektor_start = $this->jump_day($now, $tage_min - 1);
$datum_res_lektor_ende = $this->jump_day($now, $tage_bis);
$start_ymd_allowed = date('Y-m-d', $datum_res_lektor_start);
$end_ymd_allowed = date('Y-m-d', $datum_res_lektor_ende);
$result = [];
$current = strtotime(date('Y-m-d', $start_ts) . ' 00:00:00');
$end_day = strtotime(date('Y-m-d', $end_ts) . ' 00:00:00');
while ($current <= $end_day)
{
$ymd = date('Y-m-d', $current);
$result[$ymd] = ($ymd >= $start_ymd_allowed && $ymd <= $end_ymd_allowed) ? true : false;
$current = $this->jump_day($current, 1);
}
return success($result);
}
private function jump_day($timestamp, $days)
{
$days = (int)$days;
$prefix = ($days >= 0 ? '+' : '');
return strtotime($prefix . $days . ' days', $timestamp);
}
// start of the private functions ########################################################################################################
// function used to sort an array of studiensemester strings
@@ -40,7 +40,9 @@ abstract class AbstractBestandteil implements IValidation
if( is_bool($new_value) && ($old_value !== $new_value) ) {
$this->modifiedcolumns[$columnname] = $columnname;
} else if($old_value != $new_value) {
} else if(is_null($old_value) xor is_null($new_value)) {
$this->modifiedcolumns[$columnname] = $columnname;
} else if($old_value != $new_value) {
$this->modifiedcolumns[$columnname] = $columnname;
}
}
@@ -137,19 +137,25 @@ EOTXT;
return parent::__toString() . $txt;
}
/* public function validate()
public function validate()
{
if( !(filter_var($this->tage, FILTER_VALIDATE_INT,
array(
'options' => array(
'min_range' => 1,
'max_range' => 50
)
)
)) ) {
$this->validationerrors[] = 'Urlaubsanspruch muss eine Tagesanzahl im Bereich 1 bis 50 sein.';
$value = $this->vordienstzeit;
if ($value === null || $value === '') {
$result = null; // allow null value
} else {
$result = filter_var($value, FILTER_VALIDATE_INT, [
'options' => [
'min_range' => 0,
'max_range' => 100
]
]);
if ($result === false) {
$this->validationerrors[] = 'Vordienstjahre muss eine ganze Zahl (0 bis 100) enthalten oder leer sein.';
}
}
return parent::validate();
} */
}
}
@@ -11,4 +11,73 @@ class Mobilitaet_model extends DB_Model
$this->dbTable = 'bis.tbl_mobilitaet';
$this->pk = 'mobilitaet_id';
}
public function getMobilityZusatzForUids($uids) {
$qry = "SELECT distinct on(nachname, vorname, public.tbl_benutzer.person_id) uid,
tbl_mitarbeiter.mitarbeiter_uid,
tbl_note.lkt_ueberschreibbar, tbl_note.anmerkung,
tbl_mobilitaet.mobilitaetstyp_kurzbz,
(CASE WHEN bis.tbl_mobilitaet.studiensemester_kurzbz = vw_student_lehrveranstaltung.studiensemester_kurzbz THEN 1 ELSE 0 END) as doubledegree,
public.tbl_prestudent.gsstudientyp_kurzbz as ddtype,
(SELECT status_kurzbz FROM public.tbl_prestudentstatus
WHERE prestudent_id=tbl_student.prestudent_id
ORDER BY datum DESC, insertamum DESC, ext_id DESC LIMIT 1) as studienstatus
FROM
campus.vw_student_lehrveranstaltung
JOIN public.tbl_benutzer USING(uid)
JOIN public.tbl_person USING(person_id)
LEFT JOIN public.tbl_student ON(uid=student_uid)
LEFT JOIN public.tbl_mitarbeiter ON(uid=mitarbeiter_uid)
LEFT JOIN public.tbl_studentlehrverband USING(student_uid,studiensemester_kurzbz)
LEFT JOIN lehre.tbl_zeugnisnote on(vw_student_lehrveranstaltung.lehrveranstaltung_id=tbl_zeugnisnote.lehrveranstaltung_id
AND tbl_zeugnisnote.student_uid=tbl_student.student_uid
AND tbl_zeugnisnote.studiensemester_kurzbz=tbl_studentlehrverband.studiensemester_kurzbz)
LEFT JOIN lehre.tbl_note USING (note)
LEFT JOIN bis.tbl_bisio ON(uid=tbl_bisio.student_uid)
LEFT JOIN public.tbl_studiengang ON(tbl_student.studiengang_kz=tbl_studiengang.studiengang_kz)
LEFT JOIN bis.tbl_mobilitaet USING(prestudent_id)
LEFT JOIN public.tbl_prestudent USING(prestudent_id)
WHERE uid IN ?";
return $this->execReadOnlyQuery($qry, [$uids]);
}
public function formatZusatz($entry, $erhalter_kz) {
$zusatz = '';
if (isset($entry->studienstatus) && $entry->studienstatus === 'Incoming') {
$zusatz = '(i)';
}
if (isset($entry->lkt_ueberschreibbar) && $entry->lkt_ueberschreibbar === false) {
$zusatz .= ' (' . ($entry->anmerkung ?? '') . ')';
}
if (isset($entry->mitarbeiter_uid) && $entry->mitarbeiter_uid !== null) {
$zusatz .= ' (ma)';
}
if (isset($entry->stg_kz_student) && $entry->stg_kz_student == $erhalter_kz) {
$zusatz .= ' (a.o.)';
}
if (
isset($entry->mobilitaetstyp_kurzbz) && $entry->mobilitaetstyp_kurzbz &&
isset($entry->doubledegree) && $entry->doubledegree === 1
) {
$zusatz .= ' (d.d.';
$ddtype = $entry->ddtype ?? null;
if ($ddtype == 'Intern') {
$zusatz .= 'i.)';
} elseif ($ddtype == 'Extern') {
$zusatz .= 'o.)';
} else {
$zusatz .= ')';
}
}
return $zusatz;
}
}
@@ -100,12 +100,14 @@ class Abschlusspruefung_model extends DB_Model
if (isError($abschlussarbeit))
return $abschlussarbeit;
if (hasData($abschlussarbeit))
{
$abschlussarbeit = getData($abschlussarbeit)[0];
$abschlusspruefungdata->projektarbeit_studiengangstyp_name = $abschlussarbeit->projekttyp_kurzbz;
$abschlusspruefungdata->abschlussarbeit_titel = $abschlussarbeit->titel;
$abschlusspruefungdata->abschlussarbeit_note = $abschlussarbeit->note;
$abschlusspruefungdata->abschlussarbeit_sprache = $abschlussarbeit->sprache_bezeichnung;
}
}
}
@@ -52,4 +52,53 @@ class LePruefung_model extends DB_Model
'student_uid' => $student_uid
]);
}
public function getPruefungenByLvStudiensemester($lv_id, $sem_kurzbz) {
$qry = "SELECT lehre.tbl_pruefung.*, tbl_lehrveranstaltung.bezeichnung as lehrveranstaltung_bezeichnung, tbl_lehrveranstaltung.lehrveranstaltung_id,
tbl_note.bezeichnung as note_bezeichnung, tbl_pruefungstyp.beschreibung as typ_beschreibung, tbl_lehreinheit.studiensemester_kurzbz as studiensemester_kurzbz
FROM lehre.tbl_pruefung, lehre.tbl_lehreinheit, lehre.tbl_lehrveranstaltung, lehre.tbl_note, lehre.tbl_pruefungstyp
WHERE lehre.tbl_pruefung.lehreinheit_id=tbl_lehreinheit.lehreinheit_id
AND tbl_lehreinheit.lehrveranstaltung_id=tbl_lehrveranstaltung.lehrveranstaltung_id
AND lehre.tbl_pruefung.note = tbl_note.note
AND lehre.tbl_pruefung.pruefungstyp_kurzbz=tbl_pruefungstyp.pruefungstyp_kurzbz
AND tbl_lehrveranstaltung.lehrveranstaltung_id = ?
AND tbl_lehreinheit.studiensemester_kurzbz = ?
ORDER BY datum DESC;";
return $this->execReadOnlyQuery($qry, array($lv_id, $sem_kurzbz));
}
public function getPruefungenByUidTypLvStudiensemester($uid, $typ = null, $lv_id = null, $sem_kurzbz = null) {
$params = [$uid];
$qry = "SELECT tbl_pruefung.*, tbl_lehrveranstaltung.bezeichnung as lehrveranstaltung_bezeichnung, tbl_lehrveranstaltung.lehrveranstaltung_id,
tbl_note.bezeichnung as note_bezeichnung, tbl_pruefungstyp.beschreibung as typ_beschreibung, tbl_lehreinheit.studiensemester_kurzbz as studiensemester_kurzbz
FROM lehre.tbl_pruefung, lehre.tbl_lehreinheit, lehre.tbl_lehrveranstaltung, lehre.tbl_note, lehre.tbl_pruefungstyp
WHERE student_uid= ?
AND tbl_pruefung.lehreinheit_id=tbl_lehreinheit.lehreinheit_id
AND tbl_lehreinheit.lehrveranstaltung_id=tbl_lehrveranstaltung.lehrveranstaltung_id
AND tbl_pruefung.note = tbl_note.note
AND tbl_pruefung.pruefungstyp_kurzbz=tbl_pruefungstyp.pruefungstyp_kurzbz";
if ($typ != null)
{
$qry .= " AND tbl_pruefungstyp.pruefungstyp_kurzbz = ?";
$params[] = $typ;
}
if ($lv_id != null)
{
$qry .= " AND tbl_lehrveranstaltung.lehrveranstaltung_id = ?";
$params[] = $lv_id;
}
if ($sem_kurzbz != null)
{
$qry .= " AND tbl_lehreinheit.studiensemester_kurzbz = ?";
$params[] = $sem_kurzbz;
}
$qry .= " ORDER BY datum DESC";
return $this->execReadOnlyQuery($qry, $params);
}
}
@@ -739,4 +739,26 @@ EOSQL;
)";
}
public function getAllLehreinheitenForLvaAndMaUid($lva_id, $ma_uid, $sem_kurzbz)
{
$query = "SELECT DISTINCT tbl_lehreinheitmitarbeiter.lehreinheit_id, tbl_lehreinheit.lehrveranstaltung_id, tbl_lehreinheit.lehrform_kurzbz,
tbl_lehreinheitmitarbeiter.mitarbeiter_uid,
tbl_lehreinheitgruppe.semester,
tbl_lehreinheitgruppe.verband,
tbl_lehreinheitgruppe.gruppe,
tbl_lehreinheitgruppe.gruppe_kurzbz,
tbl_lehrveranstaltung.kurzbz,
tbl_studiengang.kurzbzlang,
(SELECT COUNT(DISTINCT datum) FROM campus.vw_stundenplan WHERE lehreinheit_id = lehre.tbl_lehreinheit.lehreinheit_id) as termincount,
(SELECT COUNT(*) FROM campus.vw_student_lehrveranstaltung WHERE lehreinheit_id = lehre.tbl_lehreinheit.lehreinheit_id) as studentcount
FROM lehre.tbl_lehreinheit JOIN lehre.tbl_lehreinheitmitarbeiter USING(lehreinheit_id)
JOIN lehre.tbl_lehreinheitgruppe USING(lehreinheit_id)
JOIN lehre.tbl_lehrveranstaltung USING(lehrveranstaltung_id)
JOIN public.tbl_studiengang ON (tbl_lehreinheitgruppe.studiengang_kz = tbl_studiengang.studiengang_kz)
WHERE lehrveranstaltung_id = ? AND studiensemester_kurzbz = ? AND mitarbeiter_uid = ?
ORDER BY tbl_lehreinheitgruppe.gruppe_kurzbz";
return $this->execQuery($query, [$lva_id, $sem_kurzbz, $ma_uid]);
}
}
@@ -317,7 +317,7 @@ class Lehrveranstaltung_model extends DB_Model
tbl_bisio.bisio_id, tbl_bisio.von, tbl_bisio.bis, tbl_student.studiengang_kz AS stg_kz_student,
tbl_zeugnisnote.note, tbl_mitarbeiter.mitarbeiter_uid, tbl_person.matr_nr, tbl_benutzer.uid,
UPPER(tbl_studiengang.typ::varchar(1) || tbl_studiengang.kurzbz) as kuerzel, tbl_studiengang.orgform_kurzbz, vw_student_lehrveranstaltung.semester, vw_student_lehrveranstaltung.studiensemester_kurzbz, vw_student_lehrveranstaltung.bezeichnung,
tbl_student.prestudent_id
tbl_student.prestudent_id, campus.vw_student_lehrveranstaltung.lehreinheit_id
FROM
campus.vw_student_lehrveranstaltung
JOIN public.tbl_benutzer USING(uid)
@@ -1346,4 +1346,65 @@ class Lehrveranstaltung_model extends DB_Model
return $this->execQuery($qry, $params);
}
public function getLvForLektorInSemester($sem_kurzbz, $uid) {
$qry = "SELECT DISTINCT (tbl_lehrveranstaltung.lehrveranstaltung_id),
UPPER(tbl_studiengang.typ::varchar(1) || tbl_studiengang.kurzbz) as stg_kurzbz,
tbl_lehrveranstaltung.semester as lv_semester,
tbl_lehrveranstaltung.bezeichnung as lv_bezeichnung,
(SELECT kurzbz FROM public.tbl_mitarbeiter
WHERE mitarbeiter_uid=tbl_lehreinheitmitarbeiter.mitarbeiter_uid) as lektor
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 stg_kurzbz,lv_semester,lv_bezeichnung";
return $this->execReadOnlyQuery($qry, array($sem_kurzbz, $uid));
}
// used for cis4 mylv mitarbeiter
public function getLvsByMitarbeiterInSemester($mitarbeiter_uid, $sem_kurzbz) {
$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,
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
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, $sem_kurzbz, $mitarbeiter_uid]);
}
}
@@ -14,7 +14,7 @@ class Lvgesamtnote_model extends DB_Model
}
/**
* Laedt die Noten
* Laedt die Noten - lvgesamtnote (Vorschlag) JOIN tbl.note (zeugnisnote)
*
* @param integer $lehrveranstaltung_id
* @param string $student_uid
@@ -46,4 +46,19 @@ class Lvgesamtnote_model extends DB_Model
return $this->loadWhere($where);
}
public function getLvGesamtNoteVorschlag($lehrveranstaltung_id, $student_uid, $studiensemester_kurzbz)
{
$qry = "SELECT * FROM campus.tbl_lvgesamtnote
WHERE campus.tbl_lvgesamtnote.student_uid = ?
AND campus.tbl_lvgesamtnote.studiensemester_kurzbz = ?";
$params = [$student_uid, $studiensemester_kurzbz];
if ($lehrveranstaltung_id) {
$qry .= "AND campus.tbl_lvgesamtnote.lehrveranstaltung_id = ?";
$params[] = $lehrveranstaltung_id;
}
return $this->execReadOnlyQuery($qry, $params);
}
}
+22 -1
View File
@@ -11,12 +11,33 @@ class Note_model extends DB_Model
$this->dbTable = 'lehre.tbl_note';
$this->pk = 'note';
}
public function getAllActive() {
$qry ="SELECT *
FROM lehre.tbl_note
WHERE aktiv = true";
return $this->execReadOnlyQuery($qry);
}
// used to determine the primary key of note "entschuldigt" to avoid hardcoded magic numbers
// that might differ in a different installation of fhcomplete
public function getEntschuldigtNote() {
$qry ="SELECT *
FROM lehre.tbl_note
WHERE bezeichnung = 'entschuldigt'";
return $this->execReadOnlyQuery($qry);
}
// used to determine the primary key of note "noch nicht eingetragen" to avoid hardcoded magic numbers
// that might differ in a different installation of fhcomplete
public function getNochNichtEingetragenNote() {
$qry ="SELECT *
FROM lehre.tbl_note
WHERE bezeichnung = 'Noch nicht eingetragen'";
return $this->execReadOnlyQuery($qry);
}
}
@@ -26,6 +26,9 @@ class Notenschluesselaufteilung_model extends DB_Model
$this->load->model('education/Notenschluesselzuordnung_model', 'NotenschluesselzuordnungModel');
$notenschluessel_kurzbz = $this->NotenschluesselzuordnungModel->getKurzbzForLv($lehrveranstaltung_id, $studiensemester_kurzbz);
if($notenschluessel_kurzbz == null)
return success(null);
$this->addSelect("note");
$this->addOrder("punkte", "DESC");
$this->addLimit(1);
@@ -61,6 +61,174 @@ class Paabgabe_model extends DB_Model
return $this->execReadOnlyQuery($qry, array($person_id));
}
/**
* Gets project submissions for search criteria.
* @param array $projekttyp_kurzbz_arr contains all relevant project types (e.g. Bachelor)
* @param int $studiengang_kz study program
* @param string $abgabetyp_kurzbz project submission type (e.g. end upload, intermediate submission)
* @param string $abgabedatum due date for hand-in
* @param string $personSearchString for searching by person, i.e. name, uid, person/prestudent id
* @param int $limit limiting max number of results if search criteria is not precise enough
* @return object
*/
public function getPaAbgaben(
$projekttyp_kurzbz_arr,
$studiengang_kz = null,
$abgabetyp_kurzbz = null,
$abgabedatum = null,
$personSearchString = null,
$limit = 1000
) {
$params = [];
$qry = "
SELECT
stg.bezeichnung AS stgbez, paabg.datum AS termin,
paabg.paabgabe_id, paabg.projektarbeit_id, paabg.paabgabetyp_kurzbz, paabg.abgabedatum,
abgabetyp.bezeichnung AS paabgabetyp_bezeichnung, ben.uid, pers.vorname, pers.nachname, pa.projekttyp_kurzbz, pa.titel,
UPPER(stg.typ || stg.kurzbz) AS studiengang_kuerzel,
(
/* show all relevant Studiengänge of person and wether it is an employee*/
SELECT
STRING_AGG(studiengang || ' ' || last_status, ' | ')
|| (CASE WHEN EXISTS (
SELECT 1 FROM public.tbl_mitarbeiter ma
JOIN public.tbl_benutzer ben ON ma.mitarbeiter_uid = ben.uid
WHERE person_id = prestudents.person_id
AND ben.aktiv
) THEN ' | Mitarbeiter' ELSE '' END)
FROM (
SELECT
DISTINCT person_id, prestudent_id, UPPER(stg.typ || stg.kurzbz) AS studiengang,
get_rolle_prestudent(ps.prestudent_id, null) AS last_status
FROM
public.tbl_prestudent ps
JOIN public.tbl_studiengang stg USING (studiengang_kz)
WHERE
person_id = pers.person_id
ORDER BY
prestudent_id DESC
) prestudents
WHERE
last_status IN ('Abgewiesener','Aufgenommener', 'Student', 'Incoming', 'Diplomand', 'Abbrecher', 'Unterbrecher', 'Absolvent')
GROUP BY
person_id
LIMIT 1;
) AS status
FROM
lehre.tbl_projektarbeit pa
JOIN campus.tbl_paabgabe paabg USING(projektarbeit_id)
JOIN campus.tbl_paabgabetyp abgabetyp USING(paabgabetyp_kurzbz)
LEFT JOIN public.tbl_benutzer ben ON(uid=student_uid)
LEFT JOIN public.tbl_person pers ON(ben.person_id=pers.person_id)
LEFT JOIN lehre.tbl_lehreinheit USING(lehreinheit_id)
LEFT JOIN lehre.tbl_lehrveranstaltung USING(lehrveranstaltung_id)
LEFT JOIN public.tbl_studiengang stg USING(studiengang_kz)
WHERE
TRUE";
if (isset($projekttyp_kurzbz_arr) && !isEmptyArray($projekttyp_kurzbz_arr))
{
$qry .= " AND projekttyp_kurzbz IN ?";
$params[] = $projekttyp_kurzbz_arr;
}
if (isset($studiengang_kz) && is_numeric($studiengang_kz))
{
$qry .= " AND stg.studiengang_kz=?";
$params[] = $studiengang_kz;
}
if (isset($abgabetyp_kurzbz))
{
$qry .= " AND paabg.paabgabetyp_kurzbz=?";
$params[] = $abgabetyp_kurzbz;
}
if (isset($abgabedatum))
{
$qry .= " AND paabg.datum=?";
$params[] = $abgabedatum;
}
if (is_numeric($personSearchString))
{
$personSearchString = (int) $personSearchString;
$params = array_merge($params, [$personSearchString, $personSearchString]);
$qry .= " AND (
pers.person_id = ?
OR EXISTS (SELECT 1 FROM public.tbl_prestudent WHERE person_id = pers.person_id AND prestudent_id = ?)
)";
}
elseif (is_string($personSearchString))
{
// remove empty spaces and lowercase
$personSearchString = strtolower(str_replace(' ', '', $personSearchString));
$qry .= " AND (
LOWER(REPLACE(pers.nachname || pers.vorname || pers.nachname, ' ', '')) LIKE ".$this->db->escape('%'.$personSearchString.'%')."
OR ben.uid LIKE ".$this->db->escape('%'.$personSearchString.'%')."
)";
}
$qry .= " ORDER BY nachname";
if (isset($limit) && is_numeric($limit) && (!isset($studiengang_kz) || !is_numeric($studiengang_kz)) && !isset($abgabedatum))
{
$qry .= " LIMIT ?";
$params[] = $limit;
}
return $this->execReadOnlyQuery($qry, $params);
}
/**
* Gets due dates for projekt submission search criteria.
* @param array $projekttyp_kurzbz_arr contains all relevant project types (e.g. Bachelor)
* @param int $studiengang_kz study program
* @param string $abgabetyp_kurzbz project submission type (e.g. end upload, intermediate submission)
* @return object
*/
public function getTermine($projekttyp_kurzbz_arr, $studiengang_kz, $abgabetyp_kurzbz)
{
$params = [];
$qry = "
SELECT
DISTINCT tbl_paabgabe.datum as termin, to_char(tbl_paabgabe.datum, 'DD.MM.YYYY') as termin_anzeige
FROM
lehre.tbl_projektarbeit
JOIN campus.tbl_paabgabe USING(projektarbeit_id)
LEFT JOIN public.tbl_benutzer ON(uid=student_uid)
LEFT JOIN public.tbl_person ON(tbl_benutzer.person_id=tbl_person.person_id)
LEFT JOIN lehre.tbl_lehreinheit USING(lehreinheit_id)
LEFT JOIN lehre.tbl_lehrveranstaltung USING(lehrveranstaltung_id)
LEFT JOIN public.tbl_studiengang USING(studiengang_kz)
WHERE
TRUE";
if (isset($projekttyp_kurzbz_arr) && !isEmptyArray($projekttyp_kurzbz_arr))
{
$qry .= " AND projekttyp_kurzbz IN ?";
$params[] = $projekttyp_kurzbz_arr;
}
if (isset($studiengang_kz) && is_numeric($studiengang_kz))
{
$qry .= " AND public.tbl_studiengang.studiengang_kz=?";
$params[] = $studiengang_kz;
}
if (isset($abgabetyp_kurzbz))
{
$qry .= " AND campus.tbl_paabgabe.paabgabetyp_kurzbz=?";
$params[] = $abgabetyp_kurzbz;
}
$qry .= " ORDER BY termin DESC";
return $this->execReadOnlyQuery($qry, $params);
}
public function findAbgabenNewOrUpdatedSince($interval, $relevantTypes)
{
@@ -23,9 +23,11 @@ class Projektarbeit_model extends DB_Model
*/
public function getProjektarbeit($student_uid, $studiengang_kz = null, $studiensemester_kurzbz = null, $projekttyp = null, $final = null)
{
$sprache_index = "COALESCE((SELECT index FROM public.tbl_sprache WHERE sprache=" . $this->escape(getUserLanguage()) . " LIMIT 1), 1)";
$qry = "SELECT
pa.*, tbl_projekttyp.bezeichnung,
tbl_lehreinheit.studiensemester_kurzbz, tbl_lehrveranstaltung.lehrveranstaltung_id,
tbl_sprache.bezeichnung[".$sprache_index."] AS sprache_bezeichnung,
tbl_firma.name AS firma_name,
(
SELECT
@@ -44,6 +46,7 @@ class Projektarbeit_model extends DB_Model
JOIN lehre.tbl_lehreinheit USING (lehreinheit_id)
JOIN lehre.tbl_lehrveranstaltung USING (lehrveranstaltung_id)
LEFT JOIN public.tbl_firma USING (firma_id)
LEFT JOIN public.tbl_sprache ON pa.sprache = tbl_sprache.sprache
WHERE
pa.student_uid = ?";
@@ -306,4 +306,5 @@ class Pruefung_model extends DB_Model
return $this->loadWhereCommitteeExamsFailed();
}
}
@@ -59,6 +59,37 @@ class Studienplan_model extends DB_Model
'tbl_studienplan_lehrveranstaltung.semester' => $semester
));
}
public function getStudienplanByLvaSemKurzbz($lehrveranstaltung_id, $studiensemester_kurzbz) {
$qry= "
SELECT
DISTINCT tbl_studienplan.*
FROM
lehre.tbl_studienplan
JOIN lehre.tbl_studienplan_lehrveranstaltung
USING(studienplan_id)
WHERE
tbl_studienplan_lehrveranstaltung.lehrveranstaltung_id IN (
SELECT
lv.lehrveranstaltung_id
FROM
lehre.tbl_lehrveranstaltung AS lv
LEFT JOIN lehre.tbl_lehrveranstaltung AS t ON t.lehrveranstaltung_id=lv.lehrveranstaltung_template_id
WHERE
lv.lehrtyp_kurzbz<>'tpl'
AND (lv.lehrveranstaltung_id= ? OR (lv.lehrveranstaltung_template_id= ? AND t.lehrtyp_kurzbz='tpl'))
)
AND EXISTS (
SELECT 1
FROM
lehre.tbl_studienplan_semester
WHERE studienplan_id=tbl_studienplan.studienplan_id
AND studiensemester_kurzbz= ?
AND semester = tbl_studienplan_lehrveranstaltung.semester)
ORDER BY bezeichnung";
return $this->execReadOnlyQuery($qry, array($lehrveranstaltung_id, $lehrveranstaltung_id, $studiensemester_kurzbz));
}
public function getStudienplanLehrveranstaltungForPrestudent($studienplan_id, $semester, $prestudent_id)
{
@@ -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
@@ -50,11 +50,12 @@ class Reservierung_model extends DB_Model
$query_result = $this->execReadOnlyQuery("
SELECT
'reservierung' as type, beginn, ende, datum,
DISTINCT(insertvon),
'reservierung' as type, beginn, ende, datum, array_agg(DISTINCT reservierung_id) AS reservierung_id,
COALESCE(titel, beschreibung) as topic,
array_agg(DISTINCT mitarbeiter_kurzbz) as lektor,
array_agg(DISTINCT (gruppe,verband,semester,studiengang_kz,gruppen_kuerzel)) as gruppe,
array_agg(DISTINCT(uid)) as uids,
ort_kurzbz, 'FFFFFF' as farbe
FROM
@@ -62,7 +63,7 @@ class Reservierung_model extends DB_Model
" . $subquery . "
) AS subquery
GROUP BY datum, beginn, ende, ort_kurzbz, titel, beschreibung
GROUP BY datum, beginn, ende, ort_kurzbz, titel, beschreibung, insertvon
ORDER BY datum, beginn
", is_null($ort_kurzbz) ? [$uid ?? getAuthUID(), $uid ?? getAuthUID(), $start_date, $end_date] : [$ort_kurzbz, $start_date, $end_date]);
@@ -94,11 +95,12 @@ class Reservierung_model extends DB_Model
$query_result = $this->execReadOnlyQuery("
SELECT
DISTINCT(insertvon),
'reservierung' as type, beginn, ende, datum,
COALESCE(titel, beschreibung) as topic,
array_agg(DISTINCT mitarbeiter_kurzbz) as lektor,
array_agg(DISTINCT (gruppe,verband,semester,studiengang_kz,gruppen_kuerzel)) as gruppe,
array_agg(DISTINCT(uid)) as uids,
ort_kurzbz, 'FFFFFF' as farbe
FROM
@@ -106,7 +108,7 @@ class Reservierung_model extends DB_Model
" . $subquery . "
) AS subquery
GROUP BY datum, beginn, ende, ort_kurzbz, titel, beschreibung
GROUP BY datum, beginn, ende, ort_kurzbz, titel, beschreibung, insertvon
ORDER BY datum, beginn
", [$uid ?? getAuthUID(), $start_date, $end_date]);
@@ -12,6 +12,8 @@ class Zeitsperre_model extends DB_Model
$this->pk = 'zeitsperre_id';
}
const BLOCKIERENDE_ZEITSPERREN = ['Krank','Urlaub','ZA','DienstV','PflegeU','DienstF','CovidSB','CovidKS'];
/**
* Save or update Zeitsperre.
*
@@ -61,4 +63,128 @@ class Zeitsperre_model extends DB_Model
return $this->execQuery($qry);
}
/**
* get Zeitsperren of a user
*
* @param $uid mitarbeiteruid
* @param $bisgrenze @true show only entries of actual business year (1.9.- 31.8.)
*
* @return array
*/
public function getZeitsperrenUser($uid, $bisgrenze = true)
{
$qry = "
SELECT
tbl_zeitsperre.*, tbl_zeitsperretyp.*, tbl_erreichbarkeit.farbe AS erreichbarkeit_farbe,
tbl_erreichbarkeit.beschreibung AS erreichbarkeit_beschreibung,
CONCAT (ps.vorname, ' ', ps.nachname) as vertretung
FROM (campus.tbl_zeitsperre JOIN campus.tbl_zeitsperretyp USING (zeitsperretyp_kurzbz))
LEFT JOIN campus.tbl_erreichbarkeit USING (erreichbarkeit_kurzbz)
LEFT JOIN public.tbl_benutzer ON campus.tbl_zeitsperre.vertretung_uid = public.tbl_benutzer.uid
LEFT JOIN public.tbl_person ps USING (person_id)
WHERE mitarbeiter_uid= ?
";
if($bisgrenze)
{
$qry.="
AND (
(date_part('month',vondatum)>=9 AND date_part('year', vondatum)>='".(date('Y')-1)."')
OR
(date_part('month',vondatum)<9 AND date_part('year', vondatum)>='".(date('Y'))."')
)";
}
$qry.= " ORDER BY vondatum DESC";
return $this->execQuery($qry, array('mitarbeiter_uid' => $uid));
}
/**
* check a date for existing zeitsperre
*
* @param $uid mitarbeiteruid
* @param $datum datum to check
* @param $stunde stunde (default = null)
* @param bool $nurblockierend if only hr relevante zeitsperren have to be checked
*
* @return array
*/
public function getSperreByDate($uid, $datum, $stunde = null, $nurblockierend = false)
{
$parametersArray = [$datum, $datum];
$qry = "
SELECT
*
FROM
campus.tbl_zeitsperre
WHERE
vondatum <= ?
AND bisdatum>= ?";
if($nurblockierend)
{
$qry .= " AND zeitsperretyp_kurzbz IN ('"
. implode("','", self::BLOCKIERENDE_ZEITSPERREN)
. "')";
}
if(!is_null($stunde))
{
$parametersArray = array_merge(
$parametersArray,
[$datum, $stunde, $datum, $datum, $stunde, $datum]
);
$qry.=" AND
((vondatum= ? AND vonstunde<= ? OR vonstunde is null OR vondatum<> ?) AND
(bisdatum= ? AND bisstunde>= ? OR bisstunde is null OR bisdatum<> ?))";
}
array_push($parametersArray, $uid);
$qry .= "AND mitarbeiter_uid= ? ";
return $this->execQuery($qry, $parametersArray);
}
/**
* check a date for existing zeitsperre
*
* @param $uid mitarbeiteruid
* @param $vondatum datum in Format IS0
* @param $bisdatum datum in Format ISO
*
* @return array
*/
public function existsZeitaufzeichnung($uid, $vonDay, $bisDay)
{
try {
$from = new DateTime($vonDay);
$to = new DateTime($bisDay);
} catch (Exception $e) {
throw new Exception("Invalid date format");
}
//remove hour stamps
$from->setTime(0, 0, 0);
$to->setTime(0, 0, 0)->modify('+1 day');
$fromSql = $from->format('Y-m-d');
$toSql = $to->format('Y-m-d');
$params = [$uid, $fromSql, $toSql];
$qry = "
SELECT *
FROM campus.tbl_zeitaufzeichnung
WHERE uid = ?
AND start >= ?
AND ende < ? ";
$result = $this->execQuery($qry, $params);
return $result;
}
}
@@ -11,6 +11,7 @@ $includesArray = array(
'skipID' => '#fhccontent',
'vuedatepicker11' => true,
'customCSSs' => array(
'vendor/vuejs/vuedatepicker_css/main.css',
'public/css/components/verticalsplit.css',
'public/css/components/searchbar/searchbar.css',
'public/css/Fhc.css',
@@ -23,7 +24,9 @@ $includesArray = array(
'public/css/components/FormUnderline.css',
'public/css/components/abgabetool/abgabe.css',
'public/css/Cis4/Cms.css',
'public/css/Cis4/Studium.css'
'public/css/Cis4/Studium.css',
'public/css/Cis4/Benotungstool.css',
'public/css/Cis4/Zeitsperren.css',
),
'customJSs' => array(
'vendor/npm-asset/primevue/accordion/accordion.min.js',
@@ -37,11 +40,15 @@ $includesArray = array(
'vendor/npm-asset/primevue/timeline/timeline.min.js',
'vendor/npm-asset/primevue/inplace/inplace.min.js',
'vendor/npm-asset/primevue/message/message.min.js',
'vendor/npm-asset/primevue/divider/divider.min.js',
'vendor/npm-asset/primevue/password/password.js',
'vendor/npm-asset/primevue/multiselect/multiselect.js',
'vendor/npm-asset/primevue/tieredmenu/tieredmenu.js',
'vendor/moment/luxonjs/luxon.min.js'
),
'customJSModules' => array(
'public/js/apps/Cis/Cis.js',
'vendor/olifolkerd/tabulator5/src/js/modules/ColumnCalcs/ColumnCalcs.js'
),
);
@@ -1,7 +1,5 @@
<?php
$this->load->view(
'templates/FHC-Header',
array(
$includesArray = array(
'title' => 'Lehrauftrag bestellen',
'jquery3' => true,
'jqueryui1' => true,
@@ -12,8 +10,15 @@ $this->load->view(
'dialoglib' => true,
'navigationwidget' => true,
'addons' => true,
)
);
$this->load->view(
'templates/FHC-Header',
$includesArray
);
?>
<?php echo $this->widgetlib->widget('NavigationWidget'); ?>
@@ -33,4 +38,12 @@ $this->load->view(
</div>
</div>
<?php $this->load->view('templates/FHC-Footer'); ?>
<?php
$this->load->view(
'templates/FHC-Footer',
$includesArray
);
?>
@@ -1,7 +1,6 @@
<?php
$this->load->view(
'templates/FHC-Header',
array(
$includesArray = array(
'title' => 'Lehrauftrag bestellen',
'jquery3' => true,
'bootstrap3' => true,
@@ -9,8 +8,13 @@ $this->load->view(
'sbadmintemplate3' => true,
'ajaxlib' => true,
'navigationwidget' => true,
)
);
$this->load->view(
'templates/FHC-Header',
$includesArray
);
?>
<?php echo $this->widgetlib->widget('NavigationWidget'); ?>
@@ -34,4 +38,11 @@ $this->load->view(
</div>
</div>
<?php $this->load->view('templates/FHC-Footer'); ?>
<?php
$this->load->view(
'templates/FHC-Footer',
$includesArray
);
?>
@@ -1,89 +1,101 @@
<?php
$this->load->view(
'templates/FHC-Header',
array(
'title' => 'Lehrauftrag annehmen',
'jquery3' => true,
'jqueryui1' => true,
'jquerycheckboxes1' => true,
'bootstrap5' => true,
'fontawesome6' => true,
'sbadmintemplate' => false,
'tabulator5' => true,
'tabulator5JQuery' => true,
'cis'=>true,
'momentjs2' => true,
'ajaxlib' => true,
'dialoglib' => true,
'tablewidget' => true,
'phrases' => array(
'global' => array(
'lehrauftraegeAnnehmen',
'dokumentePDF',
'PDFLehrauftraegeFH',
'PDFLehrauftraegeLehrgaenge'
),
'ui' => array(
'anzeigen',
'alleAnzeigen',
'nurBestellteAnzeigen',
'nurErteilteAnzeigen',
'nurAngenommeneAnzeigen',
'nurStornierteAnzeigen',
'hilfeZuDieserSeite',
'alleAuswaehlen',
'alleAbwaehlen',
'ausgewaehlteZeilen',
'hilfe',
'tabelleneinstellungen',
'keineDatenVorhanden',
'spaltenEinstellen',
'bestelltVon',
'erteiltVon',
'angenommenVon',
'storniertVon',
'lehrauftragInBearbeitung',
'wartetAufErteilung',
'wartetAufErneuteErteilung',
'letzterStatusBestellt',
'letzterStatusErteilt',
'letzterStatusAngenommen',
'vertragWurdeStorniert',
),
'password' => array('password'),
'dms' => array('informationsblattExterneLehrende'),
'table' => array(
'spaltenEinAusblenden',
'spaltenEinAusblendenMitKlickOeffnen',
'spaltenEinAusblendenAufEinstellungenKlicken',
'spaltenEinAusblendenMitKlickAktivieren',
'spaltenEinAusblendenMitKlickSchliessen',
'spaltenbreiteVeraendern',
'spaltenbreiteVeraendernText',
'spaltenbreiteVeraendernInfotext',
'zeilenAuswaehlen',
'zeilenAuswaehlenEinzeln',
'zeilenAuswaehlenBereich',
'zeilenAuswaehlenAlle'
),
'lehre' => array(
'lehrauftraegeAnnehmen',
'lehrauftraegeAnnehmenText',
'lehrauftraegeAnnehmenKlickStatusicon',
'lehrauftraegeAnnehmenLehrauftraegeWaehlen',
'lehrauftraegeAnnehmenMitKlickAnnehmen',
'lehrauftraegeNichtAuswaehlbar',
'lehrauftraegeNichtAuswaehlbarTextBeiAnnahme',
'filterAlleBeiAnnahme',
'filterErteiltBeiAnnahme',
'filterAngenommen'
)
$includesArray = array(
'title' => 'Lehrauftrag annehmen',
'jquery3' => true,
'jqueryui1' => true,
'jquerycheckboxes1' => true,
'bootstrap5' => true,
'fontawesome6' => true,
'sbadmintemplate' => false,
'tabulator5' => true,
'tabulator5JQuery' => true,
'cis'=>true,
'momentjs2' => true,
'ajaxlib' => true,
'dialoglib' => true,
'tablewidget' => true,
'phrases' => array(
'global' => array(
'lehrauftraegeAnnehmen',
'dokumentePDF',
'PDFLehrauftraegeFH',
'PDFLehrauftraegeLehrgaenge'
),
'customJSs' => array(
'public/js/bootstrapper.js',
'public/js/lehre/lehrauftrag/acceptLehrauftrag.js')
'ui' => array(
'anzeigen',
'alleAnzeigen',
'nurBestellteAnzeigen',
'nurErteilteAnzeigen',
'nurAngenommeneAnzeigen',
'nurStornierteAnzeigen',
'hilfeZuDieserSeite',
'alleAuswaehlen',
'alleAbwaehlen',
'ausgewaehlteZeilen',
'hilfe',
'tabelleneinstellungen',
'keineDatenVorhanden',
'spaltenEinstellen',
'bestelltVon',
'erteiltVon',
'angenommenVon',
'storniertVon',
'lehrauftragInBearbeitung',
'wartetAufErteilung',
'wartetAufErneuteErteilung',
'letzterStatusBestellt',
'letzterStatusErteilt',
'letzterStatusAngenommen',
'vertragWurdeStorniert',
),
'password' => array('password'),
'dms' => array('informationsblattExterneLehrende'),
'table' => array(
'spaltenEinAusblenden',
'spaltenEinAusblendenMitKlickOeffnen',
'spaltenEinAusblendenAufEinstellungenKlicken',
'spaltenEinAusblendenMitKlickAktivieren',
'spaltenEinAusblendenMitKlickSchliessen',
'spaltenbreiteVeraendern',
'spaltenbreiteVeraendernText',
'spaltenbreiteVeraendernInfotext',
'zeilenAuswaehlen',
'zeilenAuswaehlenEinzeln',
'zeilenAuswaehlenBereich',
'zeilenAuswaehlenAlle'
),
'lehre' => array(
'lehrauftraegeAnnehmen',
'lehrauftraegeAnnehmenText',
'lehrauftraegeAnnehmenKlickStatusicon',
'lehrauftraegeAnnehmenLehrauftraegeWaehlen',
'lehrauftraegeAnnehmenMitKlickAnnehmen',
'lehrauftraegeNichtAuswaehlbar',
'lehrauftraegeNichtAuswaehlbarTextBeiAnnahme',
'filterAlleBeiAnnahme',
'filterErteiltBeiAnnahme',
'filterAngenommen'
)
),
'customJSs' => array(
'public/js/bootstrapper.js',
'public/js/lehre/lehrauftrag/acceptLehrauftrag.js'
)
);
if (defined("CIS4")) {
$this->load->view(
'templates/CISVUE-Header',
$includesArray
);
} else {
$this->load->view(
'templates/FHC-Header',
$includesArray
);
}
?>
@@ -230,5 +242,17 @@ $this->load->view(
</div><!-- end page-wrapper -->
<br>
<?php $this->load->view('templates/FHC-Footer'); ?>
<?php
if (defined("CIS4")) {
$this->load->view(
'templates/CISVUE-Footer',
$includesArray
);
} else {
$this->load->view(
'templates/FHC-Footer',
$includesArray
);
}
?>
@@ -1,98 +1,101 @@
<?php
$includesArray = array(
'title' => 'Lehrauftrag erteilen',
'jquery3' => true,
'jqueryui1' => true,
'jquerycheckboxes1' => true,
'bootstrap3' => true,
'fontawesome6' => true,
'sbadmintemplate3' => true,
'tabulator5' => true,
'tabulator5JQuery' => true,
'momentjs2' => true,
'ajaxlib' => true,
'dialoglib' => true,
'tablewidget' => true,
'navigationwidget' => true,
'phrases' => array(
'global' => array(
'lehrauftraegeErteilen',
'mehrHilfe',
'weitereInformationenUnter'
),
'ui' => array(
'anzeigen',
'alleAnzeigen',
'nurNeueAnzeigen',
'nurBestellteAnzeigen',
'nurErteilteAnzeigen',
'nurAngenommeneAnzeigen',
'nurGeaenderteAnzeigen',
'nurDummiesAnzeigen',
'hilfeZuDieserSeite',
'alleAuswaehlen',
'alleAbwaehlen',
'ausgewaehlteZeilen',
'hilfe',
'tabelleneinstellungen',
'keineDatenVorhanden',
'spaltenEinstellen',
'bestelltVon',
'erteiltVon',
'angenommenVon',
'stundenStundensatzGeaendert',
'neuerLehrauftragOhneLektorVerplant',
'wartetAufBestellung',
'wartetAufErneuteBestellung',
'neuerLehrauftragWartetAufBestellung',
'letzterStatusBestellt',
'letzterStatusErteilt',
'letzterStatusAngenommen',
),
'table' => array(
'spaltenEinAusblenden',
'spaltenEinAusblendenMitKlickOeffnen',
'spaltenEinAusblendenAufEinstellungenKlicken',
'spaltenEinAusblendenMitKlickAktivieren',
'spaltenEinAusblendenMitKlickSchliessen',
'spaltenbreiteVeraendern',
'spaltenbreiteVeraendernText',
'spaltenbreiteVeraendernInfotext',
'zeilenAuswaehlen',
'zeilenAuswaehlenEinzeln',
'zeilenAuswaehlenBereich',
'zeilenAuswaehlenAlle'
),
'lehre' => array(
'lehrauftragStandardBestellprozess',
'lehrauftragStandardBestellprozessBestellen',
'lehrauftragStandardBestellprozessErteilen',
'lehrauftragStandardBestellprozessAnnehmen',
'lehrauftraegeErteilen',
'lehrauftraegeErteilenText',
'lehrauftraegeErteilenKlickStatusicon',
'lehrauftraegeErteilenLehrauftraegeWaehlen',
'lehrauftraegeErteilenMitKlickErteilen',
'geaenderteLehrauftraege',
'geaenderteLehrauftraegeTextBeiErteilung',
'lehrauftraegeNichtAuswaehlbar',
'lehrauftraegeNichtAuswaehlbarTextBeiErteilung',
'filterAlle',
'filterNeu',
'filterBestellt',
'filterErteilt',
'filterAngenommen',
'filterGeaendert',
'filterDummies'
)
),
'customJSs' => array(
'public/js/bootstrapper.js',
'public/js/lehre/lehrauftrag/approveLehrauftrag.js'
)
);
$this->load->view(
'templates/FHC-Header',
array(
'title' => 'Lehrauftrag erteilen',
'jquery3' => true,
'jqueryui1' => true,
'jquerycheckboxes1' => true,
'bootstrap3' => true,
'fontawesome6' => true,
'sbadmintemplate3' => true,
'tabulator5' => true,
'tabulator5JQuery' => true,
'momentjs2' => true,
'ajaxlib' => true,
'dialoglib' => true,
'tablewidget' => true,
'navigationwidget' => true,
'phrases' => array(
'global' => array(
'lehrauftraegeErteilen',
'mehrHilfe',
'weitereInformationenUnter'
),
'ui' => array(
'anzeigen',
'alleAnzeigen',
'nurNeueAnzeigen',
'nurBestellteAnzeigen',
'nurErteilteAnzeigen',
'nurAngenommeneAnzeigen',
'nurGeaenderteAnzeigen',
'nurDummiesAnzeigen',
'hilfeZuDieserSeite',
'alleAuswaehlen',
'alleAbwaehlen',
'ausgewaehlteZeilen',
'hilfe',
'tabelleneinstellungen',
'keineDatenVorhanden',
'spaltenEinstellen',
'bestelltVon',
'erteiltVon',
'angenommenVon',
'stundenStundensatzGeaendert',
'neuerLehrauftragOhneLektorVerplant',
'wartetAufBestellung',
'wartetAufErneuteBestellung',
'neuerLehrauftragWartetAufBestellung',
'letzterStatusBestellt',
'letzterStatusErteilt',
'letzterStatusAngenommen',
),
'table' => array(
'spaltenEinAusblenden',
'spaltenEinAusblendenMitKlickOeffnen',
'spaltenEinAusblendenAufEinstellungenKlicken',
'spaltenEinAusblendenMitKlickAktivieren',
'spaltenEinAusblendenMitKlickSchliessen',
'spaltenbreiteVeraendern',
'spaltenbreiteVeraendernText',
'spaltenbreiteVeraendernInfotext',
'zeilenAuswaehlen',
'zeilenAuswaehlenEinzeln',
'zeilenAuswaehlenBereich',
'zeilenAuswaehlenAlle'
),
'lehre' => array(
'lehrauftragStandardBestellprozess',
'lehrauftragStandardBestellprozessBestellen',
'lehrauftragStandardBestellprozessErteilen',
'lehrauftragStandardBestellprozessAnnehmen',
'lehrauftraegeErteilen',
'lehrauftraegeErteilenText',
'lehrauftraegeErteilenKlickStatusicon',
'lehrauftraegeErteilenLehrauftraegeWaehlen',
'lehrauftraegeErteilenMitKlickErteilen',
'geaenderteLehrauftraege',
'geaenderteLehrauftraegeTextBeiErteilung',
'lehrauftraegeNichtAuswaehlbar',
'lehrauftraegeNichtAuswaehlbarTextBeiErteilung',
'filterAlle',
'filterNeu',
'filterBestellt',
'filterErteilt',
'filterAngenommen',
'filterGeaendert',
'filterDummies'
)
),
'customJSs' => array(
'public/js/bootstrapper.js',
'public/js/lehre/lehrauftrag/approveLehrauftrag.js'
)
)
'templates/FHC-Header',
$includesArray
);
?>
@@ -208,5 +211,13 @@ $this->load->view(
</div><!-- end page-wrapper -->
<br>
<?php $this->load->view('templates/FHC-Footer'); ?>
<?php
$this->load->view(
'templates/FHC-Footer',
$includesArray
);
?>
@@ -1,98 +1,101 @@
<?php
$includesArray = array(
'title' => 'Lehrauftrag bestellen',
'jquery3' => true,
'jqueryui1' => true,
'jquerycheckboxes1' => true,
'bootstrap3' => true,
'fontawesome6' => true,
'sbadmintemplate3' => true,
'tabulator5' => true,
'tabulator5JQuery' => true,
'momentjs2' => true,
'ajaxlib' => true,
'dialoglib' => true,
'tablewidget' => true,
'navigationwidget' => true,
'phrases' => array(
'global' => array(
'lehrauftraegeBestellen',
'mehrHilfe',
'weitereInformationenUnter'
),
'ui' => array(
'anzeigen',
'alleAnzeigen',
'nurNeueAnzeigen',
'nurBestellteAnzeigen',
'nurErteilteAnzeigen',
'nurAngenommeneAnzeigen',
'nurGeaenderteAnzeigen',
'nurDummiesAnzeigen',
'hilfeZuDieserSeite',
'alleAuswaehlen',
'alleAbwaehlen',
'ausgewaehlteZeilen',
'hilfe',
'tabelleneinstellungen',
'keineDatenVorhanden',
'spaltenEinstellen',
'bestelltVon',
'erteiltVon',
'angenommenVon',
'neuerLehrauftragOhneLektorVerplant',
'neuerLehrauftragWartetAufBestellung',
'letzterStatusBestellt',
'letzterStatusErteilt',
'letzterStatusAngenommen',
'nachAenderungStundensatzStunden',
'vorAenderungStundensatzStunden'
),
'table' => array(
'spaltenEinAusblenden',
'spaltenEinAusblendenMitKlickOeffnen',
'spaltenEinAusblendenAufEinstellungenKlicken',
'spaltenEinAusblendenMitKlickAktivieren',
'spaltenEinAusblendenMitKlickSchliessen',
'spaltenbreiteVeraendern',
'spaltenbreiteVeraendernText',
'spaltenbreiteVeraendernInfotext',
'zeilenAuswaehlen',
'zeilenAuswaehlenEinzeln',
'zeilenAuswaehlenBereich',
'zeilenAuswaehlenAlle'
),
'lehre' => array(
'lehrauftragStandardBestellprozess',
'lehrauftragStandardBestellprozessBestellen',
'lehrauftragStandardBestellprozessErteilen',
'lehrauftragStandardBestellprozessAnnehmen',
'lehrauftraegeBestellen',
'lehrauftraegeBestellenText',
'lehrauftraegeBestellenKlickStatusicon',
'lehrauftraegeBestellenLehrauftraegeWaehlen',
'lehrauftraegeBestellenMitKlickBestellen',
'lehrauftraegeBestellenVertragWirdAngelegt',
'geaenderteLehrauftraege',
'geaenderteLehrauftraegeText',
'lehrauftraegeNichtAuswaehlbar',
'lehrauftraegeNichtAuswaehlbarText',
'filterAlle',
'filterNeu',
'filterBestellt',
'filterErteilt',
'filterAngenommen',
'filterGeaendert',
'filterDummies'
)
),
'customJSs' => array(
'public/js/bootstrapper.js',
'public/js/lehre/lehrauftrag/orderLehrauftrag.js'
)
);
$this->load->view(
'templates/FHC-Header',
array(
'title' => 'Lehrauftrag bestellen',
'jquery3' => true,
'jqueryui1' => true,
'jquerycheckboxes1' => true,
'bootstrap3' => true,
'fontawesome6' => true,
'sbadmintemplate3' => true,
'tabulator5' => true,
'tabulator5JQuery' => true,
'momentjs2' => true,
'ajaxlib' => true,
'dialoglib' => true,
'tablewidget' => true,
'navigationwidget' => true,
'phrases' => array(
'global' => array(
'lehrauftraegeBestellen',
'mehrHilfe',
'weitereInformationenUnter'
),
'ui' => array(
'anzeigen',
'alleAnzeigen',
'nurNeueAnzeigen',
'nurBestellteAnzeigen',
'nurErteilteAnzeigen',
'nurAngenommeneAnzeigen',
'nurGeaenderteAnzeigen',
'nurDummiesAnzeigen',
'hilfeZuDieserSeite',
'alleAuswaehlen',
'alleAbwaehlen',
'ausgewaehlteZeilen',
'hilfe',
'tabelleneinstellungen',
'keineDatenVorhanden',
'spaltenEinstellen',
'bestelltVon',
'erteiltVon',
'angenommenVon',
'neuerLehrauftragOhneLektorVerplant',
'neuerLehrauftragWartetAufBestellung',
'letzterStatusBestellt',
'letzterStatusErteilt',
'letzterStatusAngenommen',
'nachAenderungStundensatzStunden',
'vorAenderungStundensatzStunden'
),
'table' => array(
'spaltenEinAusblenden',
'spaltenEinAusblendenMitKlickOeffnen',
'spaltenEinAusblendenAufEinstellungenKlicken',
'spaltenEinAusblendenMitKlickAktivieren',
'spaltenEinAusblendenMitKlickSchliessen',
'spaltenbreiteVeraendern',
'spaltenbreiteVeraendernText',
'spaltenbreiteVeraendernInfotext',
'zeilenAuswaehlen',
'zeilenAuswaehlenEinzeln',
'zeilenAuswaehlenBereich',
'zeilenAuswaehlenAlle'
),
'lehre' => array(
'lehrauftragStandardBestellprozess',
'lehrauftragStandardBestellprozessBestellen',
'lehrauftragStandardBestellprozessErteilen',
'lehrauftragStandardBestellprozessAnnehmen',
'lehrauftraegeBestellen',
'lehrauftraegeBestellenText',
'lehrauftraegeBestellenKlickStatusicon',
'lehrauftraegeBestellenLehrauftraegeWaehlen',
'lehrauftraegeBestellenMitKlickBestellen',
'lehrauftraegeBestellenVertragWirdAngelegt',
'geaenderteLehrauftraege',
'geaenderteLehrauftraegeText',
'lehrauftraegeNichtAuswaehlbar',
'lehrauftraegeNichtAuswaehlbarText',
'filterAlle',
'filterNeu',
'filterBestellt',
'filterErteilt',
'filterAngenommen',
'filterGeaendert',
'filterDummies'
)
),
'customJSs' => array(
'public/js/bootstrapper.js',
'public/js/lehre/lehrauftrag/orderLehrauftrag.js'
)
)
'templates/FHC-Header',
$includesArray
);
?>
@@ -209,5 +212,11 @@ $this->load->view(
</div><!-- end page-wrapper -->
<br>
<?php $this->load->view('templates/FHC-Footer'); ?>
<?php
$this->load->view(
'templates/FHC-Footer',
$includesArray
);
?>
+36 -25
View File
@@ -9,22 +9,8 @@ $sitesettings = array(
'ajaxlib' => true,
'sbadmintemplate3' => true,
'phrases' => array(
'abschlusspruefung' => array(
'freigegebenAm',
'pruefungGespeichert',
'pruefungSpeichernFehler',
'abschlussbeurteilungLeer',
'beginnzeitLeer',
'beginnzeitFormatError',
'endezeitLeer',
'endezeitFormatError',
'endezeitBeforeError',
'verfNotice'
),
'ui' => array(
'stunde',
'minute'
)
'abschlusspruefung',
'ui'
),
'customCSSs' => array(
'public/css/sbadmin2/admintemplate_contentonly.css',
@@ -37,10 +23,18 @@ $sitesettings = array(
)
);
$this->load->view(
'templates/FHC-Header',
$sitesettings
);
if(defined('CIS4')){
$this->load->view(
'templates/CISVUE-Header',
$sitesettings
);
}else{
$this->load->view(
'templates/FHC-Header',
$sitesettings
);
}
?>
<div id="wrapper">
<div id="page-wrapper">
@@ -161,6 +155,14 @@ $this->load->view(
<?php echo ($abschlusspruefung->studiengangstyp == 'Bachelor' ? $this->p->t('abschlusspruefung', 'pruefungsgegenstandBachelor') : $this->p->t('abschlusspruefung', 'pruefungsgegenstandMaster')) ?>
</td>
</tr>
<tr>
<td>
<?php echo $this->p->t('abschlusspruefung', 'spracheDerArbeit') ?>&nbsp;<?php echo $arbeit_name ?>
</td>
<td colspan="5">
<?php echo $abschlusspruefung->abschlussarbeit_sprache ?? '' ?>
</td>
</tr>
<tr>
<td colspan="6">
<?php echo ucfirst($this->p->t('global', 'notizen')); ?>
@@ -201,7 +203,9 @@ $this->load->view(
<div class="col-lg-12 text-right">
<p>
<?php $freigegeben = isset($abschlusspruefung->freigabedatum); ?>
<button id="saveProtocolBtn" class="btn btn-default"<?php echo $freigegeben ? " disabled" : "" ?>><?php echo $this->p->t('ui', 'speichern') ?></button>
<button id="saveProtocolBtn" class="btn btn-default"<?php echo $freigegeben ? ' disabled title="'.$this->p->t('abschlusspruefung', 'bereitsFreigegeben').'"' : '' ?>>
<?php echo $this->p->t('ui', 'speichern') ?>
</button>
</p>
</div>
</div>
@@ -236,7 +240,14 @@ $this->load->view(
</div>
</div>
<?php
$this->load->view(
'templates/FHC-Footer',
$sitesettings
);
if (defined('CIS4')) {
$this->load->view(
'templates/CISVUE-Footer',
$sitesettings
);
} else {
$this->load->view(
'templates/FHC-Footer',
$sitesettings
);
}
+1 -1
View File
@@ -1,4 +1,4 @@
<div class="navbar-default sidebar" role="navigation">
<div class="navbar-default sidebar top-0" role="navigation">
<div class="sidebar-nav navbar-collapse">
<ul class="nav" id="side-menu"></ul>
</div>
+4
View File
@@ -533,5 +533,9 @@
"phpmetrics/phpmetrics": "2.*",
"sebastian/phpcpd": "3.*",
"phpunit/phpunit": "^6"
},
"scripts": {
"post-install-cmd": "@symlink_vendor_to_public",
"symlink_vendor_to_public": "ln -sfn ../vendor ./public/"
}
}
+49 -1
View File
@@ -25,6 +25,7 @@
*/
require_once(dirname(__FILE__).'/basis_db.class.php');
require_once(dirname(__FILE__).'/'.EXT_FKT_PATH.'/generateZahlungsreferenz.inc.php');
require_once(dirname(__FILE__).'/variable.class.php');
class konto extends basis_db
{
@@ -432,6 +433,8 @@ class konto extends basis_db
$qry.=" ORDER BY beschreibung";
$oehBeitrag = $this->_getOEHBeitrag();
if($this->db_query($qry))
{
while($row = $this->db_fetch_object())
@@ -440,7 +443,15 @@ class konto extends basis_db
$typ->buchungstyp_kurzbz = $row->buchungstyp_kurzbz;
$typ->beschreibung = $row->beschreibung;
$typ->standardbetrag = $row->standardbetrag;
if (strtolower($typ->buchungstyp_kurzbz) === 'oeh' && $oehBeitrag)
{
$typ->standardbetrag = $oehBeitrag;
}
else
{
$typ->standardbetrag = $row->standardbetrag;
}
$typ->standardtext = $row->standardtext;
$typ->credit_points = $row->credit_points;
$typ->aktiv = $this->db_parse_bool($row->aktiv);
@@ -990,6 +1001,43 @@ class konto extends basis_db
return false;
}
}
private function _getOEHBeitrag()
{
if(!is_user_logged_in())
{
return false;
}
$variablen_obj = new variable();
$variablen_obj->loadVariables(get_uid());
$qry = "WITH semstart AS (
SELECT start FROM public.tbl_studiensemester
WHERE studiensemester_kurzbz = '". $this->db_escape($variablen_obj->variable->semester_aktuell) . "'
)
SELECT * FROM bis.tbl_oehbeitrag oehb
JOIN public.tbl_studiensemester semvon ON oehb.von_studiensemester_kurzbz = semvon.studiensemester_kurzbz
LEFT JOIN public.tbl_studiensemester sembis ON oehb.bis_studiensemester_kurzbz = sembis.studiensemester_kurzbz
JOIN semstart ON semstart.start::date >= semvon.start::date AND (sembis.studiensemester_kurzbz IS NULL OR semstart.start::date <= sembis.start::date)
ORDER BY semvon.start
LIMIT 1";
if ($this->db_query($qry))
{
if($row = $this->db_fetch_object())
{
$summe = ($row->studierendenbeitrag + $row->versicherung) * -1;
return number_format((float)$summe, 2, '.', '');
}
return false;
}
else
{
$this->errormsg = 'Fehler bei der Abfrage aufgetreten';
return false;
}
}
}
?>
+47
View File
@@ -0,0 +1,47 @@
/* 1. Stick the Header */
#notentable .tabulator-header .tabulator-col.sticky-col {
position: sticky;
left: 0;
z-index: 10; /* Must be higher than other headers */
background-color: #fff; /* Opaque background is required */
border-right: 2px solid #ddd; /* Optional: Separator line */
}
/* 2. Stick the Data Cells */
#notentable .tabulator-tableholder .tabulator-row .tabulator-cell.sticky-col {
position: sticky;
left: 0;
z-index: 10; /* Ensure it floats above other cells */
background-color: #fff; /* Match your row background color */
border-right: 2px solid #ddd; /* Optional: Separator line */
}
/* 3. Fix for Hover Effects (Optional) */
/* If you use hover rows, you need to ensure the sticky cell matches the hover color */
#notentable .tabulator-row:hover .tabulator-cell.sticky-col {
background-color: #ccc; /* Match your existing hover color */
}
/* styling for points input column for notenvorschläge in benotungstool*/
#notentable .tabulator-tableholder .tabulator-editable[tabulator-field="punkte"] {
position: relative;
background-color: rgba(255, 255, 157, 0.73);
}
/* styling for editable dropdown column of notenvorschläge in benotungstool*/
#notentable .tabulator-tableholder .tabulator-editable[tabulator-field="note_vorschlag"] {
position: relative;
background-color: rgba(255, 255, 157, 0.73);
cursor: pointer;
}
#notentable .tabulator-tableholder .tabulator-editable[tabulator-field="note_vorschlag"]::after {
content: "▾";
position: absolute;
right: 6px;
color: rgba(176, 176, 106, 0.73);;
font-size: x-large;
bottom: 6px;
pointer-events: none;
}
+17 -34
View File
@@ -388,12 +388,6 @@ html {
#nav-search > .input-group > * {
border-radius: 0 !important;
}
#nav-search .searchbar_results {
top: 100% !important;
left: 0;
right: 0 !important;
width: 100% !important;
}
/* frame */
.in-frame {
@@ -467,7 +461,14 @@ html {
/* overflow: visible !important; */
}
#cis-header {
z-index: 10;
z-index: 10;
}
#cis-header-bar {
position: fixed;
top: 0;
height: var(--fhc-cis-header-height);
width: 100%;
background-color: var(--fhc-primary);
}
#cis-header nav {
position: initial;
@@ -483,12 +484,7 @@ html {
display: none;
}
#nav-logo {
position: fixed;
top: 0;
left: 0;
height: var(--fhc-cis-header-height);
width: var(--fhc-cis-menu-width);
background-color: var(--fhc-primary);
padding: var(--fhc-cis-header-py) var(--fhc-cis-header-px);
z-index: 2;
}
@@ -503,37 +499,27 @@ html {
top: var(--fhc-cis-header-height);
display: flex;
flex-direction: column;
}
#nav-main-sticky > :not(#nav-main-toggle) {
overflow: auto;
height: 100%;
}
#nav-main-toggle {
width: 0;
z-index: 1;
}
#nav-main-toggle .btn,
#nav-main-toggle .fa-arrow-circle-left {
#nav-main-toggle:hover {
background-color: var(--fhc-secondary-highlight);
}
#nav-main-toggle .div,
#nav-main-toggle .fa-chevron-left {
transition: all 0.5s ease-in-out;
}
#nav-main-toggle .collapsed.btn {
background-color: transparent !important;
}
#nav-main-toggle .collapsed .fa-arrow-circle-left {
#nav-main-toggle .collapsed .fa-chevron-left {
transform: scaleX(-1);
color: var(--fhc-black-40);
}
#nav-search {
position: fixed;
top: 0;
left: var(--fhc-cis-menu-width);
height: var(--fhc-cis-header-height);
right: calc(var(--fhc-cis-header-height) + 2 * var(--fhc-cis-header-px) - 2 * var(--fhc-cis-header-py));
width: auto !important;
}
#nav-user {
position: fixed;
top: 0;
right: 0;
position: relative;
}
#nav-user-btn {
border-width: 0;
@@ -568,7 +554,7 @@ html {
#nav-main-menu {
height: 100%;
background-color: var(--fhc-cis-menu-bg);
background-color: var(--fhc-primary);
}
#nav-main-menu > div {
width: var(--fhc-cis-menu-width);
@@ -610,9 +596,6 @@ html {
}
#nav-user{
position: relative;
}
#nav-user-btn {
}
#nav-user-btn img {
object-fit: cover;
+14
View File
@@ -97,3 +97,17 @@
display: none;
}
.fhc-calendar-empty-slot-plus {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
font-size: 18px;
opacity: 0;
pointer-events: none;
user-select: none;
}
.part-body:hover .fhc-calendar-empty-slot-plus {
opacity: 1;
}
+39
View File
@@ -0,0 +1,39 @@
/* Repositioning clear icon Datepicker for not being outside the textfield */
/* Wrapper */
.dp__input_wrap {
position: relative;
}
/* calender-Icon left */
.dp__input_icon {
position: absolute;
left: 10px;
right: auto;
top: 50%;
transform: translateY(-50%);
}
/* Clear-Icon right */
.dp__clear_icon {
position: absolute;
right: 10px;
left: auto;
top: 50%;
transform: translateY(-50%);
}
/* padding for Icons */
.dp__input {
padding-left: 36px !important;
padding-right: 36px !important;
}
.info-feedback {
display: block;
font-size: 0.875em;
color: #0d6efd; /* Bootstrap primary */
}
.is-info {
border-color: #0d6efd;
}
-4
View File
@@ -197,10 +197,6 @@ html.fs_huge {
margin-bottom: -1px;
}
.tiny-90 div.tox.tox-tinymce {
height: 90% !important;
}
/* slim begin */
.stv .form-label {
margin-bottom: .15rem;
+10
View File
@@ -6,3 +6,13 @@
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;
}
.mylv-semester-table .tabulator-cell .action-col {
/*min-height: 2.5rem;*/
align-items: flex-start; /* so wrapped rows don't stretch vertically */
}
+15
View File
@@ -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 }
};
}
};
@@ -0,0 +1,44 @@
export default {
getReservableMap(ort_kurzbz, start_date, end_date) {
return {
method: 'post',
url: `/api/frontend/v1/calendar/RoomPlan/getReservableMap/${ort_kurzbz}`,
params: { start_date, end_date }
};
},
getRoomCreationInfo() {
return {
method: 'get',
url: '/api/frontend/v1/calendar/RoomPlan/getRoomCreationInfo'
};
},
getGruppen(query) {
return {
method: 'get',
url: `/api/frontend/v1/calendar/RoomPlan/getGruppen?query=${encodeURIComponent(query)}`
};
},
getLektor(query) {
return {
method: 'get',
url: `/api/frontend/v1/calendar/RoomPlan/getLektor?query=${encodeURIComponent(query)}`
};
},
addRoomReservation(formData) {
return {
method: 'post',
url: '/api/frontend/v1/calendar/RoomPlan/addRoomReservation',
params: formData
};
},
deleteRoomReservation(reservierung_id) {
return {
method: 'post',
url: '/api/frontend/v1/calendar/RoomPlan/deleteRoomReservation',
params: {
reservierung_id: reservierung_id
}
};
}
}
+70
View File
@@ -0,0 +1,70 @@
/**
* Copyright (C) 2026 fhcomplete.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export default {
getTimelocksUser(uid) {
return {
method: 'get',
url: '/api/frontend/v1/Zeitsperren/getZeitsperrenUser/' + uid
};
},
getTypenZeitsperren(){
return {
method: 'get',
url: '/api/frontend/v1/Zeitsperren/getTypenZeitsperren/'
};
},
getTypenErreichbarkeit(){
return {
method: 'get',
url: '/api/frontend/v1/Zeitsperren/getTypenErreichbarkeit/'
};
},
getStunden(){
return {
method: 'get',
url: '/api/frontend/v1/Zeitsperren/getStunden/'
};
},
addZeitsperre(uid, params) {
return {
method: 'post',
url: '/api/frontend/v1/Zeitsperren/add/' + uid,
params
};
},
editZeitsperre(zeitsperre_id, params) {
return {
method: 'post',
url: '/api/frontend/v1/Zeitsperren/update/' + zeitsperre_id,
params
};
},
loadZeitsperre(zeitsperre_id) {
return {
method: 'get',
url: '/api/frontend/v1/Zeitsperren/loadZeitsperre/' + zeitsperre_id
};
},
deleteZeitsperre(zeitsperre_id) {
return {
method: 'post',
url: '/api/frontend/v1/Zeitsperren/delete/' + zeitsperre_id
};
}
};
+21 -1
View File
@@ -35,5 +35,25 @@ export default {
method: 'get',
url: `/api/frontend/v1/Lehre/Pruefungen/${lehrveranstaltung_id}`
};
},
getSemesterAverageGrade(semester) {
return {
method: 'get',
url: `/api/frontend/v1/Lehre/semesterAverageGrade/${semester}`
}
},
getZugewieseneLv(uid, sem_kurzbz){
return {
method: 'get',
url: '/api/frontend/v1/Lehre/getZugewieseneLv',
params: { uid, sem_kurzbz}
};
},
getLeForLv(lv_id, sem_kurzbz) {
return {
method: 'get',
url: '/api/frontend/v1/Lehre/getLeForLv',
params: { lv_id, sem_kurzbz }
};
}
};
};
+87
View File
@@ -0,0 +1,87 @@
/**
* Copyright (C) 2025 fhcomplete.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export default {
getCisConfig(){
return {
method: 'get',
url: '/api/frontend/v1/Noten/getCisConfig'
};
},
getStudentenNoten(lv_id, sem_kurzbz) {
return {
method: 'get',
url: '/api/frontend/v1/Noten/getStudentenNoten',
params: { lv_id, sem_kurzbz }
};
},
getNoten(){
return {
method: 'get',
url: '/api/frontend/v1/Noten/getNoten'
};
},
saveStudentenNoten(password, noten, lv_id, sem_kurzbz) {
return {
method: 'post',
url: '/api/frontend/v1/Noten/saveStudentenNoten',
params: { password, noten, lv_id, sem_kurzbz }
};
},
saveNotenvorschlag(lv_id, sem_kurzbz, student_uid, note, punkte = null) {
return {
method: 'post',
url: '/api/frontend/v1/Noten/saveNotenvorschlag',
params: { lv_id, sem_kurzbz, student_uid, note, punkte }
};
},
saveStudentPruefung(student_uid, note, punkte, datum, lva_id, lehreinheit_id, sem_kurzbz, typ){
return {
method: 'post',
url: '/api/frontend/v1/Noten/saveStudentPruefung',
params: { student_uid, note, punkte, datum, lva_id, lehreinheit_id, sem_kurzbz, typ }
};
},
createPruefungen(uids, datum, lva_id, sem_kurzbz){
return {
method: 'post',
url: '/api/frontend/v1/Noten/createPruefungen',
params: { uids, datum, lva_id, sem_kurzbz }
};
},
saveNotenvorschlagBulk(lv_id, sem_kurzbz, noten) {
return {
method: 'post',
url: '/api/frontend/v1/Noten/saveNotenvorschlagBulk',
params: { lv_id, sem_kurzbz, noten }
};
},
saveStudentPruefungBulk(lv_id, sem_kurzbz, pruefungen) {
return {
method: 'post',
url: '/api/frontend/v1/Noten/savePruefungenBulk',
params: { lv_id, sem_kurzbz, pruefungen }
};
},
getNoteByPunkte(punkte, lv_id, sem_kurzbz) {
return {
method: 'post',
url: '/api/frontend/v1/Noten/getNoteByPunkte',
params: { punkte, lv_id, sem_kurzbz }
};
}
}
@@ -0,0 +1,36 @@
export default {
getPaAbgaben(studiengang_kz, abgabetyp_kurzbz, abgabedatum, personSearchString) {
return {
method: 'get',
url: '/api/frontend/v1/education/PaabgabeUebersicht/getPaAbgaben',
params: {
studiengang_kz: studiengang_kz, abgabetyp_kurzbz: abgabetyp_kurzbz, abgabedatum: abgabedatum, personSearchString: personSearchString
}
};
},
getStudiengaenge() {
return {
method: 'get',
url: '/api/frontend/v1/education/PaabgabeUebersicht/getStudiengaenge'
};
},
getTermine(studiengang_kz, abgabetyp_kurzbz) {
return {
method: 'get',
url: '/api/frontend/v1/education/PaabgabeUebersicht/getTermine',
params: { studiengang_kz: studiengang_kz, abgabetyp_kurzbz: abgabetyp_kurzbz }
};
},
getPaAbgabetypen() {
return {
method: 'get',
url: '/api/frontend/v1/education/PaabgabeUebersicht/getPaAbgabetypen'
};
},
getViewData() {
return {
method: 'get',
url: '/api/frontend/v1/education/PaabgabeUebersicht/viewData'
};
}
};
+7 -1
View File
@@ -29,5 +29,11 @@ export default {
url: 'api/frontend/v1/organisation/studiensemester/getAll',
params: { order, start }
};
}
},
getStudiensemester() {
return {
method: 'get',
url: '/api/frontend/v1/Studiensemester/getStudiensemester'
};
},
};
+15 -3
View File
@@ -38,6 +38,10 @@ export default {
};
},
insert(params) {
if(params.betrag)
{
params.betrag = params.betrag.replace(',', '.');
}
return {
method: 'post',
url: 'api/frontend/v1/stv/konto/insert',
@@ -52,6 +56,10 @@ export default {
};
},
edit(params) {
if(params.betrag)
{
params.betrag = params.betrag.replace(',', '.');
}
return {
method: 'post',
url: 'api/frontend/v1/stv/konto/update',
@@ -65,10 +73,14 @@ export default {
params: { buchungsnr }
};
},
getBuchungstypen() {
getBuchungstypen(studiensemester_kurzbz) {
let url = 'api/frontend/v1/stv/konto/getBuchungstypen'
if (!!studiensemester_kurzbz)
url = url + '/' + encodeURIComponent(studiensemester_kurzbz);
return {
method: 'get',
url: 'api/frontend/v1/stv/konto/getBuchungstypen'
url: url
};
}
},
};
+51 -22
View File
@@ -5,7 +5,7 @@ import contrast from '../../directives/contrast.js';
import {setScrollbarWidth} from "../../helpers/CssVarCalcHelpers.js";
import LvPlan from "../../components/Cis/LvPlan/Lehrveranstaltung.js";
import MyLvPlan from "../../components/Cis/LvPlan/MyLvPlan.js";
import MylvStudent from "../../components/Cis/Mylv/Student.js";
import Mylv from "../../components/Cis/Mylv/MyLv.js";
import Profil from "../../components/Cis/Profil/Profil.js";
import Raumsuche from "../../components/Cis/Raumsuche/Raumsuche.js";
import CmsNews from "../../components/Cis/Cms/News.js";
@@ -19,9 +19,13 @@ import DeadlineOverview from "../../components/Cis/Abgabetool/DeadlineOverview.j
import Studium from "../../components/Cis/Studium/Studium.js";
import StgOrgLvPlan from "../../components/Cis/LvPlan/StgOrg.js";
import OtherLvPlan from "../../components/Cis/LvPlan/OtherLvPlan.js";
import PaabgabeUebersicht from "../../components/Cis/ProjektabgabeUebersicht/ProjektabgabeUebersicht.js";
import Benotungstool from "../../components/Cis/Benotungstool/Benotungstool.js";
import Zeitsperren from "../../components/Cis/Zeitsperren/Zeitsperren.js";
import ApiRouteInfo from '../../api/factory/routeinfo.js';
import {capitalize} from "../../helpers/StringHelpers.js";
import ApiAuthinfo from "../../api/factory/authinfo.js";
const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
const isMobile = window.matchMedia("(max-width: 767px)").matches;
@@ -77,6 +81,18 @@ const router = VueRouter.createRouter({
component: Raumsuche,
props: true
},
{
path: `/Cis/ProjektabgabeUebersicht`,
name: 'PaabgabeUebersicht',
component: PaabgabeUebersicht,
props: true
},
{
path: `/Cis/Benotungstool/:lv_id/:sem_kurzbz`,
name: 'Benotungstool',
component: Benotungstool,
props: true
},
// Redirect old links to new format
{
path: "/CisVue/Cms/getRoomInformation/:ort_kurzbz",
@@ -151,7 +167,7 @@ const router = VueRouter.createRouter({
{
path: `/Cis/MyLv/:studiensemester?`,
name: 'MyLv',
component: MylvStudent,
component: Mylv,
props: true,
},
{
@@ -163,7 +179,7 @@ const router = VueRouter.createRouter({
// Redirect old links to new format
{
// only trigger on first param being numeric to avoid paths like "LvPlan/Month" entering here
path: "/Cis/LvPlan/:lv_id(\\d+)",
path: "/Cis/LvPlan/:lv_id(\\d+)",
name: "LvPlanOld",
component: LvPlan,
redirect(to) {
@@ -245,6 +261,12 @@ const router = VueRouter.createRouter({
};
},
},
{
path: `/Cis/Zeitsperren`,
name: 'Zeitsperren',
component: Zeitsperren,
props: true
},
]
})
@@ -252,20 +274,16 @@ const app = Vue.createApp({
name: 'CisApp',
data: () => ({
appSideMenuEntries: {},
renderers: null,
windowWidth: 0,
isStudent: null,
isMitarbeiter: null,
}),
components: {},
computed: {
isMobile() {
const smallScreen = window.matchMedia("(max-width: 767px)").matches;
const touchCapable = ("ontouchstart" in window) || navigator.maxTouchPoints > 0;
return smallScreen;// && touchCapable;
},
},
provide() {
return { // provide injectable & watchable language property
language: Vue.computed(() => this.$p.user_language),
isMobile: this.isMobile,
isMobile: Vue.computed(() => this.windowWidth < 767),
isStudent: Vue.computed(() => this.isStudent),
isMitarbeiter: Vue.computed(() => this.isMitarbeiter)
}
},
methods: {
@@ -279,7 +297,7 @@ const app = Vue.createApp({
if(target?.id == 'skiplink') return
if (target && this.isInternalRoute(target.href)) {
const url = new URL(target.href)
const path = url.pathname
const base = this.$router.options.history.base
const route = path.replace(base, '') || '/'
@@ -287,28 +305,39 @@ const app = Vue.createApp({
// let click event propagate normally if we dont route internally
const res = this.$router.resolve(route)
if(!res?.matched?.length || res.name === 'Fallback') return
event.preventDefault(); // Prevent browser navigation
if(this.isMobile) { // toggle the menu
const navMain = document.getElementById('nav-main');
// fix unwanted toggle from off to on for some links on mobile
if(navMain.classList.contains('show')){
document.getElementById('nav-main-btn').click();
}
}
}
this.$router.push(route);
}
}
},
handleWindowResize() {
this.windowWidth = window.innerWidth;
},
},
mounted() {
async created() {
this.windowWidth = window.innerWidth;
await this.$api.call(ApiAuthinfo.getAuthInfo()).then((res) => {
this.isMitarbeiter = res.data.isMitarbeiter;
this.isStudent = res.data.isStudent;
});
},
async mounted() {
document.addEventListener('click', this.handleClick);
window.addEventListener("resize", this.handleWindowResize);
},
beforeUnmount() {
document.removeEventListener('click', this.handleClick);
window.removeEventListener("resize", this.handleWindowResize);
},
});
+15 -10
View File
@@ -134,26 +134,27 @@ const app = Vue.createApp({
childactions: []
}
}
}
},
windowWidth: 0,
};
},
computed: {
isMobile() {
const smallScreen = window.matchMedia("(max-width: 767px)").matches;
const touchCapable = ("ontouchstart" in window) || navigator.maxTouchPoints > 0;
return smallScreen;// && touchCapable;
},
},
provide() {
return {
isMobile: this.isMobile
isNarrow: Vue.computed(() => this.windowWidth < 992),
isMobile: Vue.computed(() => this.windowWidth < 767),
}
},
methods: {
searchfunction: function(searchsettings) {
return this.$api.call(ApiSearchbar.searchCis(searchsettings));
}
},
handleWindowResize() {
this.windowWidth = window.innerWidth;
},
},
created() {
this.windowWidth = window.innerWidth;
},
async mounted() {
const openOtherLvPlanAction = {
label: Vue.computed(() => this.$p.t("lehre/stundenplan")),
@@ -183,6 +184,10 @@ const app = Vue.createApp({
openOtherLvPlanAction,
);
}
window.addEventListener("resize", this.handleWindowResize);
},
beforeUnmount() {
window.removeEventListener("resize", this.handleWindowResize);
},
});
+1 -1
View File
@@ -148,4 +148,4 @@ export default {
</div>
</div>
`
}
}
+14 -3
View File
@@ -44,7 +44,10 @@ export default {
return () => true;
}),
hasDragoverFunc: Vue.computed(() => this.onDragover),
mode: Vue.computed(() => this.mode)
mode: Vue.computed(() => this.mode),
reservierbarMap: Vue.computed(() => this.reservierbarMap),
isReservierbar: Vue.computed(() => this.isReservierbar),
createContext: Vue.computed(() => this.createContext)
};
},
props: {
@@ -97,7 +100,13 @@ export default {
draggableEvents: [Boolean, Array, Function],
dropableEvents: [Boolean, Array, Function],
onDragover: Function,
onDrop: Function
onDrop: Function,
isReservierbar: Boolean,
createContext: Object,
reservierbarMap: {
type: Object,
default: () => ({})
},
},
emits: [
"click:next",
@@ -105,11 +114,13 @@ export default {
"click:mode",
"click:event",
"click:day",
"click:slot",
"click:week",
"update:date",
"update:mode",
"update:range",
"drop"
"drop",
"create-event"
],
data() {
return {
+38 -2
View File
@@ -2,6 +2,7 @@ import GridLine from './Grid/Line.js';
import GridLineEvent from './Grid/Line/Event.js';
import CalDnd from '../../../directives/Calendar/DragAndDrop.js';
import CalClick from '../../../directives/Calendar/Click.js';
export default {
name: "CalendarGrid",
@@ -10,12 +11,16 @@ export default {
GridLineEvent
},
directives: {
CalDnd
CalDnd,
CalClick
},
inject: {
originalEvents: "events",
originalBackgrounds: "backgrounds",
dropAllowed: "dropAllowed"
dropAllowed: "dropAllowed",
timezone: "timezone",
reservierbar: "isReservierbar",
reservierbarMap: "reservierbarMap",
},
provide() {
return {
@@ -308,8 +313,25 @@ export default {
} else {
this.$refs.scroller.scrollTo(0, 0);
}
},
isFreeSlot(date, part, dayEvents) {
const pastEnd = luxon.DateTime.now().setZone(this.timezone);
const start = date.plus(part.start || part);
const end = date.plus(part.end || part.plus({ hours: 1 }));
if (start < pastEnd)
return false;
if (!dayEvents || !dayEvents.length)
return true;
return !dayEvents.some(ev => ev.start < end && ev.end > start);
}
},
created() {
this.$p.loadCategory(["LvPlan"]);
},
beforeUnmount() {
this.disableAutoScroll();
},
@@ -400,6 +422,20 @@ export default {
:style="'grid-' + axisCol + ':' + (1+index) + ';grid-' + axisRow + ':ps_' + i + '/pe_' + i"
>
<slot name="part-body" v-bind="{ index, part }" />
<div
v-if="isFreeSlot(date, part, eventsNormal[index]) && reservierbar"
class="fhc-calendar-empty-slot"
style="position:absolute; inset:0; z-index:1"
v-cal-click:slot="{ date, part }"
:title="this.reservierbarMap?.[date.toISODate()] ? $p.t('LvPlan/add_reservation') : $p.t('LvPlan/reservation_not_allowed')"
>
<div class="fhc-calendar-empty-slot-plus">
<i v-if="this.reservierbarMap?.[date.toISODate()]" class="fa-solid fa-plus"></i>
<i v-else class="fa-solid fa-ban" style="color: red;"></i>
</div>
</div>
<div
v-if="snapToGrid && dragging"
style="position:absolute;inset:0;z-index:1"
@@ -147,12 +147,13 @@ export default {
:style="'grid-' + axisRow + ': ' + event.rows.join('/')"
class="d-flex flex-row justify-content-center gap-1 align-items-center"
>
<i
<span
v-for="(subEvent, subEventIndex) in event.events"
:key="subEventIndex"
class="fa-solid fa-circle fa-2xs"
:style="subEvent.farbe ? {color: '#' + subEvent.farbe} : {}"
></i>
:style="subEvent.farbe ? {'background-color': '#' + subEvent.farbe} : {}"
style="height:10px; width:10px;"
class="border border-dark rounded-circle"
></span>
</div>
<div
v-else-if="event.display === 'compactedExtra'"
+42 -3
View File
@@ -28,7 +28,15 @@ export default {
getPromiseFunc: {
type: Function,
required: true
}
},
reservierbar: {
type: Boolean,
default: false
},
createContext: {
type: Object,
default: () => ({})
},
},
provide() {
return {
@@ -43,11 +51,26 @@ export default {
emits: [
"update:date",
"update:mode",
"update:range"
"update:range",
"create-event",
"delete-event",
'update:reservierbarMap'
],
data() {
return {
timezone: FHC_JS_DATA_STORAGE_OBJECT.timezone,
isReservierbar: Vue.computed(() => {
if (!this.reservierbar)
return false;
if (!this.reservierbarMap)
return false;
if (typeof this.reservierbarMap === 'object')
return Object.keys(this.reservierbarMap).length > 0;
return false;
}),
modeOptions: {
day: {
emptyMessage: Vue.computed(() => this.$p.t('lehre/noLvFound')),
@@ -125,11 +148,14 @@ export default {
);
this.compactibleEventTypes = compactibleEventTypesResponse.data;
},
closeModal() {
this.$refs.calendar.hideEventModal();
},
},
setup(props, context) {
const rangeInterval = Vue.ref(null);
const { events, lv, reset } = useEventLoader(rangeInterval, props.getPromiseFunc);
const { events, lv, reservierbarMap, reset } = useEventLoader(rangeInterval, props.getPromiseFunc);
Vue.watch(lv, newValue => {
context.emit('update:lv', newValue);
@@ -137,10 +163,15 @@ export default {
const { renderers } = useRenderers();
Vue.watch(reservierbarMap, newVal => {
context.emit('update:reservierbarMap', newVal);
});
return {
rangeInterval,
events,
lv,
reservierbarMap,
reset,
renderers
};
@@ -162,6 +193,9 @@ export default {
:events="events || []"
:backgrounds="backgrounds"
:time-grid="teachingunits"
:reservierbar-map="reservierbarMap"
:isReservierbar="isReservierbar"
:create-context="createContext"
show-btns
@update:date="(newDate, newMode) => $emit('update:date', newDate, newMode)"
@update:mode="(newMode, newDate) => $emit('update:mode', newMode, newDate)"
@@ -184,6 +218,7 @@ export default {
v-if="mode == 'event'"
:is="renderers[event.type]?.modalContent"
:event="event"
@create-event="(event) => $emit('create-event', event)"
></component>
<component
v-else-if="mode == 'eventheader'"
@@ -194,6 +229,10 @@ export default {
v-else
:is="renderers[event.type]?.calendarEvent"
:event="event"
@delete-event="(event) => $emit('delete-event', event)"
:timeSlotDisplayBehavior="
$props.mode.toLowerCase() === 'list' ? 'always' : 'default'
"
></component>
</div>
</template>
+34 -1
View File
@@ -16,7 +16,19 @@ export default {
inject: {
timeGrid: "timeGrid",
originalEvents: "events",
timezone: "timezone"
timezone: "timezone",
reservierbar: {
from: "isReservierbar",
default: false
},
reservierbarMap: {
type: Object,
default: () => ({})
},
createContext: {
from: 'createContext',
default: () => {}
},
},
props: {
day: {
@@ -103,6 +115,27 @@ export default {
});
}
}
else if (evt.detail.source == 'slot')
{
if (!this.reservierbar)
return;
const { date, part } = evt.detail.value || {};
if (!date)
return;
let reservierbar = this.reservierbarMap?.[date.toISODate()] === true;
if (!reservierbar)
return;
this.$emit('requestModalOpen', {
event: {
type: this.createContext?.scope ?? 'slot',
start: date.plus(part.start || part),
end: date.plus(part.end || part.plus({ hours: 1 })),
createContext: this.createContext
}
});
}
}
},
setup() {
+35 -8
View File
@@ -7,6 +7,14 @@ export default {
BaseSlider,
WeekView
},
inject: {
reservierbar: "isReservierbar",
reservierbarMap: "reservierbarMap",
createContext: {
from: 'createContext',
default: () => {}
},
},
props: {
currentDate: {
type: luxon.DateTime,
@@ -83,14 +91,33 @@ export default {
},
handleClickDefaults(evt) {
switch (evt.detail.source) {
case 'day':
// default: Set current-date
this.$emit('update:currentDate', evt.detail.value);
break;
case 'event':
// default: Request Modal
this.$emit('requestModalOpen', { event: evt.detail.value });
break;
case 'day':
// default: Set current-date
this.$emit('update:currentDate', evt.detail.value);
break;
case 'event':
// default: Request Modal
this.$emit('requestModalOpen', { event: evt.detail.value });
break;
case 'slot':
{
const { date, part } = evt.detail.value || {};
if (!date)
return;
let reservierbar = this.reservierbarMap?.[date.toISODate()] === true;
if (!reservierbar)
return;
this.$emit('requestModalOpen', {
event: {
type: this.createContext?.scope ?? 'slot',
start: date.plus(part.start || part),
end: date.plus(part.end || part.plus({ hours: 1 })),
createContext: this.createContext
}
});
break;
}
}
}
},
+1
View File
@@ -97,6 +97,7 @@ export default {
<component
:is="renderers[event.type]?.calendarEvent"
:event="event"
:timeSlotDisplayBehavior="'always'"
></component>
</div>
</template>
@@ -522,41 +522,20 @@ export const AbgabetoolAssistenz = {
const table = this.$refs.abgabeTable.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();
// setup change eventlisteners
const events = [
"columnMoved", "columnResized", "columnVisibilityChanged",
"filterChanged", "headerFilterChanged", "dataSorted",
"columnSorted", "sortersChanged"
];
events.forEach(eventName => {
table.on(eventName, () => this.saveState(table));
});
table.on("renderComplete", () => {
if(!this.stateRestored) {
@@ -999,7 +978,6 @@ export const AbgabetoolAssistenz = {
this.tableData = this.mapProjekteToTableData(this.projektarbeiten)
await this.tableBuiltPromise
this.$refs.abgabeTable.tabulator.setData(this.tableData);
},
loadProjektarbeiten(all = false, callback) {
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -109,7 +109,7 @@ export default {
<h2 ref="newsPageHeading" class="fhc-primary-color">News</h2>
<hr/>
<pagination
v-show="content?true:false"
v-if="content?true:false"
:page="page"
:page_size="page_size"
@pageUpdated="afterPageUpdated($event)"
@@ -127,7 +127,7 @@ export default {
</div>
</div>
<pagination
v-show="content?true:false"
v-if="content?true:false"
:page="page"
:page_size="page_size"
@pageUpdated="afterPageUpdated($event)"
@@ -29,51 +29,51 @@ components:{
},
template:/*html*/`
<div id="fhc-studiengang-informationen">
<template v-if="studiengang?.bezeichnung && semester">
<div class="card card-body mb-3 border-0">
<div class="mb-1">
<h2 class="h4 mb-1 pb-0">{{$p.t('lehre','studiengang')}}:</h2>
<span class="mb-1">{{studiengang?.bezeichnung}}</span>
</div>
<div class="mb-1">
<h2 class="h4 mb-1 pb-0">Moodle:</h2>
<a class="fhc-link-color mb-1" target="_blank" :href="moodleLink">{{studiengang?.kurzbzlang}}</a>
</div>
<div :class="{'mb-1':studiengang?.zusatzinfo_html}">
<h2 class="h4 mb-1 pb-0">{{$p.t('lehre','studiensemester')}}: </h2>
<span class="mb-1">{{semester}}</span>
</div>
<div class="zusatzinfo" v-if="studiengang?.zusatzinfo_html" v-html="studiengang?.zusatzinfo_html"></div>
</div>
</template>
<template v-for="{title, collection} in collection_array">
<template v-if="Array.isArray(collection) && collection.length !==0">
<h2 class="h5 text-truncate">{{title}}</h2>
<template v-if="displayWidget">
<div class="d-flex flex-wrap flex-row mb-3 gap-2">
<template v-for="person in collection">
<studiengang-person displayWidget v-bind="person"></studiengang-person>
</template>
<template v-if="studiengang?.bezeichnung && semester">
<div class="card card-body mb-3 border-0">
<div class="mb-1">
<h2 class="h4 mb-1 pb-0">{{$p.t('lehre','studiengang')}}:</h2>
<span class="mb-1">{{studiengang?.bezeichnung}}</span>
</div>
</template>
<template v-else>
<template v-for="person in collection">
<div class="mb-3">
<studiengang-person v-bind="person"></studiengang-person>
<div class="mb-1">
<h2 class="h4 mb-1 pb-0">Moodle:</h2>
<a class="fhc-link-color mb-1" target="_blank" :href="moodleLink">{{studiengang?.kurzbzlang}}</a>
</div>
<div :class="{'mb-1':studiengang?.zusatzinfo_html}">
<h2 class="h4 mb-1 pb-0">{{$p.t('lehre','studiensemester')}}: </h2>
<span class="mb-1">{{semester}}</span>
</div>
<div class="zusatzinfo" v-if="studiengang?.zusatzinfo_html" v-html="studiengang?.zusatzinfo_html"></div>
</div>
</template>
<template v-for="{title, collection} in collection_array">
<template v-if="Array.isArray(collection) && collection.length !==0">
<h2 class="h5 text-truncate">{{title}}</h2>
<template v-if="displayWidget">
<div class="d-flex flex-wrap flex-row mb-3 gap-2">
<template v-for="person in collection">
<studiengang-person displayWidget v-bind="person"></studiengang-person>
</template>
</div>
</template>
<template v-else>
<template v-for="person in collection">
<div class="mb-3">
<studiengang-person v-bind="person"></studiengang-person>
</div>
</template>
</template>
</template>
</template>
</template>
<template v-if="hochschulvertr && Array.isArray(hochschulvertr) && hochschulvertr.length >0">
<studiengang-vertretung showBezeichnung :title="$p.t('studiengangInformation', 'Hochschulvertretung')" :vertretungsList="hochschulvertr"></studiengang-vertretung>
</template>
<template v-if="stdv && Array.isArray(stdv) && stdv.length >0">
<studiengang-vertretung :title="$p.t('studiengangInformation', 'Studienvertretung').concat(studiengang.kurzbzlang??'')" :vertretungsList="stdv"></studiengang-vertretung>
</template>
<template v-if="jahrgangsvertr && Array.isArray(jahrgangsvertr) && jahrgangsvertr.length >0">
<studiengang-vertretung :title="$p.t('studiengangInformation', 'Jahrgangsvertretung')" :vertretungsList="jahrgangsvertr"></studiengang-vertretung>
</template>
<template v-if="hochschulvertr && Array.isArray(hochschulvertr) && hochschulvertr.length >0">
<studiengang-vertretung showBezeichnung :title="$p.t('studiengangInformation', 'Hochschulvertretung')" :vertretungsList="hochschulvertr"></studiengang-vertretung>
</template>
<template v-if="stdv && Array.isArray(stdv) && stdv.length >0">
<studiengang-vertretung :title="$p.t('studiengangInformation', 'Studienvertretung').concat(studiengang?.kurzbzlang??'')" :vertretungsList="stdv"></studiengang-vertretung>
</template>
<template v-if="jahrgangsvertr && Array.isArray(jahrgangsvertr) && jahrgangsvertr.length >0">
<studiengang-vertretung :title="$p.t('studiengangInformation', 'Jahrgangsvertretung')" :vertretungsList="jahrgangsvertr"></studiengang-vertretung>
</template>
</div>
`,
@@ -27,7 +27,7 @@ export default {
<dl class="stgkontaktinfo">
<dt><i class="fa fa-phone me-2"></i></dt>
<dd class="mb-3"><a class="fhc-link-color" :href="phone.link">{{phone.number}}</a></dd>
<dd class="mb-3"><a class="fhc-link-color" :href="phone?.link">{{phone?.number}}</a></dd>
<dt><i class="fa fa-home me-2"></i></dt>
<dd class="mb-3">{{ort}}</dd>
@@ -54,6 +54,17 @@ export default {
return this.lv.bezeichnung;
}
},
watch: {
async isMobile() {
await this.$nextTick();
this.handleChangeMode(
this.currentMode,
luxon.DateTime.fromISO(this.currentDay, {
zone: this.timezone,
}),
);
},
},
methods: {
handleChangeDate(day, newMode) {
return this.handleChangeMode(newMode, day);
@@ -82,6 +82,17 @@ export default {
];
}
},
watch: {
async isMobile() {
await this.$nextTick();
this.handleChangeMode(
this.currentMode,
luxon.DateTime.fromISO(this.currentDay, {
zone: this.timezone,
}),
);
},
},
methods: {
handleChangeDate(day, newMode) {
return this.handleChangeMode(newMode, day);
@@ -137,6 +137,15 @@ export default {
this.$router.go();
},
},
async isMobile() {
await this.$nextTick();
this.handleChangeMode(
this.currentMode,
luxon.DateTime.fromISO(this.currentDay, {
zone: this.timezone,
}),
);
},
},
methods: {
handleChangeDate(day, newMode) {
+15
View File
@@ -130,6 +130,17 @@ export default {
];
},
},
watch: {
async isMobile() {
await this.$nextTick();
this.handleChangeMode(
this.currentMode,
luxon.DateTime.fromISO(this.currentDay, {
zone: this.timezone,
}),
);
},
},
methods: {
loadLvPlan() {
if (!this.formData.stgkz) {
@@ -308,6 +319,10 @@ export default {
this.loadListGroup();
}
}
if (this.formData.stgkz) {
this.loadLvPlan();
}
},
template: `
<div class="cis-lvplan-stg-org d-flex flex-column h-100">
+87 -57
View File
@@ -30,7 +30,7 @@ export default {
menuOpen:true,
};
},
inject: ["isMobile"],
inject: ["isNarrow", "isMobile"],
provide(){
return{
setActiveEntry: this.setActiveEntry,
@@ -113,71 +113,101 @@ export default {
});
},
template: /*html*/`
<div
id="header-options-collapsible"
class="collapse multi-collapse collapse-horizontal show"
>
<div class="d-flex flex-row align-items-center gap-2 h-100" style="width: 79px">
<button id="nav-main-btn" class="navbar-toggler rounded-0 px-2 border-0" type="button" data-bs-toggle="offcanvas" data-bs-target="#nav-main" aria-controls="nav-main" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<span v-if="isMobile" class="d-flex flex-row align-items-center">
<theme-switch></theme-switch>
</span>
<div id="cis-header-bar" class="d-flex flex-row flex-grow-1">
<div id="nav-logo" class="d-none d-lg-block">
<div class="d-flex h-100 justify-content-between">
<a :href="rootUrl">
<img :src="logoUrl" alt="Corporate Identity Logo">
</a>
</div>
</div>
</div>
<fhc-searchbar
:searchoptions="searchbaroptions"
:searchfunction="searchfunction"
ref="searchbar"
id="nav-search"
class="fhc-searchbar w-100 py-1 py-lg-2"
></fhc-searchbar>
<div id="nav-logo" class="d-none d-lg-block">
<div class="d-flex h-100 justify-content-between">
<a :href="rootUrl">
<img :src="logoUrl" alt="Corporate Identity Logo">
</a>
<theme-switch></theme-switch>
</div>
</div>
<div
id="header-usermenu-collapsible"
class="collapse multi-collapse collapse-horizontal show"
>
<div
:style="!isMobile ? '' : 'width: 51px'"
id="nav-user"
v-if="isNarrow"
:class="{'collapse multi-collapse collapse-horizontal show': isMobile}"
id="navbar-toggler-collapsible"
>
<button id="nav-user-btn" class="btn btn-link rounded-0" type="button" data-bs-toggle="collapse" data-bs-target="#nav-user-menu" aria-expanded="false" aria-controls="nav-user-menu">
<img :src="avatarUrl" :alt="$p.t('profilUpdate/profilBild')" class="bg-dark avatar rounded-circle border border-dark"/>
</button>
<ul ref="navUserDropdown"
@[\`shown.bs.collapse\`]="handleShowNavUser"
@[\`hide.bs.collapse\`]="handleHideNavUser"
id="nav-user-menu" class="top-100 end-0 collapse list-unstyled" aria-labelledby="nav-user-btn">
<li><a class="fhc-dark-bg btn rounded-0 d-block" :href="site_url + '/Cis/Profil'" id="menu-profil">Profil</a></li>
<li >
<cis-sprachen @languageChanged="fetchMenu"></cis-sprachen>
</li>
<li><hr class="dropdown-divider m-0 "></li>
<li ><a class="fhc-dark-bg btn rounded-0 d-block" :href="logoutUrl">Logout</a></li>
</ul>
<div class="d-flex flex-row align-items-center h-100" style="width: 35px">
<button id="nav-main-btn" class="navbar-toggler rounded-0 px-2 border-0" type="button" data-bs-toggle="offcanvas" data-bs-target="#nav-main" aria-controls="nav-main" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
</div>
</div>
<fhc-searchbar
:searchoptions="searchbaroptions"
:searchfunction="searchfunction"
ref="searchbar"
id="nav-search"
class="fhc-searchbar flex-grow-1 py-1 py-lg-2"
>
<template #collapseToggler="{ isSearchShownInMobileView }">
<span
v-if="isMobile"
type="button"
data-bs-toggle="collapse"
data-bs-target=".multi-collapse"
aria-controls="searchbar-collapsible navbar-toggler-collapsible options-collapsible"
aria-expanded="false"
class="d-flex flex-row align-items-center pe-1"
style="color: white"
>
<i v-if="isSearchShownInMobileView" class="fa-solid fa-chevron-left ps-3"></i>
<i v-else class="fa-solid fa-magnifying-glass ps-2"></i>
</span>
</template>
</fhc-searchbar>
<div
id="options-collapsible"
:class="{'collapse multi-collapse collapse-horizontal show': isMobile}"
>
<div :style="!isMobile ? '' : 'width: 105px'" class="d-flex flex-row ps-3 justify-content-end">
<span class="d-flex flex-row align-items-center">
<theme-switch></theme-switch>
</span>
<div id="nav-user">
<button id="nav-user-btn" class="btn btn-link rounded-0" type="button" data-bs-toggle="collapse" data-bs-target="#nav-user-menu" aria-expanded="false" aria-controls="nav-user-menu">
<img :src="avatarUrl" :alt="$p.t('profilUpdate/profilBild')" class="bg-dark avatar rounded-circle border border-dark"/>
</button>
<ul ref="navUserDropdown"
@[\`shown.bs.collapse\`]="handleShowNavUser"
@[\`hide.bs.collapse\`]="handleHideNavUser"
id="nav-user-menu" class="top-100 end-0 collapse list-unstyled" aria-labelledby="nav-user-btn">
<li><a class="fhc-dark-bg btn rounded-0 d-block" :href="site_url + '/Cis/Profil'" id="menu-profil">Profil</a></li>
<li >
<cis-sprachen @languageChanged="fetchMenu"></cis-sprachen>
</li>
<li><hr class="dropdown-divider m-0 "></li>
<li ><a class="fhc-dark-bg btn rounded-0 d-block" :href="logoutUrl">Logout</a></li>
</ul>
</div>
</div>
</div>
</div>
<nav id="nav-main" class="offcanvas offcanvas-start" tabindex="-1" aria-labelledby="nav-main-btn" data-bs-backdrop="false">
<div id="nav-main-sticky">
<div id="nav-main-toggle" class="position-static d-none d-lg-block ">
<button :aria-label="menuCollapseAriaLabel" type="button" @click="menuOpen = !menuOpen" class="btn text-light rounded-0 p-1 d-flex align-items-center" data-bs-toggle="collapse" data-bs-target=".nav-menu-collapse" aria-expanded="true" aria-controls="nav-sprachen nav-main-menu">
<i aria-hidden="true" class="fa fa-arrow-circle-left fhc-text"></i>
</button>
</div>
<div class="offcanvas-body p-0">
<div id="nav-main-menu" class="nav-menu-collapse collapse collapse-horizontal show">
<div>
<cis-menu-entry :highestMatchingUrlCount="highestMatchingUrlCount" :activeContent="activeEntry" v-for="entry in entries" :key="entry.content_id" :entry="entry" />
<div class="d-flex flex-row h-100">
<div class="offcanvas-body p-0">
<div id="nav-main-menu" class="nav-menu-collapse collapse collapse-horizontal show">
<div class="flex-grow-1">
<cis-menu-entry :highestMatchingUrlCount="highestMatchingUrlCount" :activeContent="activeEntry" v-for="entry in entries" :key="entry.content_id" :entry="entry" />
</div>
</div>
</div>
<div id="nav-main-toggle" class="d-none d-lg-block">
<div
@click="menuOpen = !menuOpen"
:aria-label="menuCollapseAriaLabel"
type="button"
class="h-100 d-flex align-items-center px-2"
data-bs-toggle="collapse"
data-bs-target=".nav-menu-collapse"
aria-expanded="true"
aria-controls="nav-sprachen nav-main-menu"
>
<i aria-hidden="true" class="fa-solid fa-chevron-left fhc-text"></i>
</div>
</div>
</div>
+1
View File
@@ -1,5 +1,6 @@
export default {
name: 'LvMenu',
props:{
menu:{
type:Array,
@@ -4,7 +4,7 @@ import LvMenu from "./LvMenu.js";
import ApiAddons from '../../../api/factory/addons.js';
export default {
name: 'LvUebersicht',
props:{
event:{
type:Object,
+182
View File
@@ -0,0 +1,182 @@
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
export default {
name: 'MyLv',
components: {
MylvSemesterCards,
MylvTable
},
data: () => {
return {
firstLoad: true,
studiensemester: null,
lvs: {},
currentSemester: null,
mode: localStorage.getItem('myLvaDefaultMode') ?? 'cards'
};
},
provide() {
return {
type: Vue.computed(() => this.type),
}
},
inject: ['isStudent', 'isMitarbeiter'],
computed: {
type() {
if(this.isStudent) return 'student'
if(this.isMitarbeiter) return 'employee'
return null
},
ready() {
return this.studiensemester !== null && (!this.firstLoad || this.current.lvs !== null);
},
current() {
if (this.currentSemester === null)
return { semester: null, lvs: [] };
if (this.lvs[this.currentSemester] === undefined) {
this.lvs[this.currentSemester] = {
semester: this.currentSemester,
lvs: null
};
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.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
}
})
})
})
}
return this.lvs[this.currentSemester];
},
nearestSem() {
let now = Date.now();
let nearestSem = null;
let nearestSemDiff = 0;
this.studiensemester.forEach(sem => {
let start = new Date(sem.start);
let end = new Date(sem.ende);
if (now >= start && now <= end) {
nearestSem = sem.studiensemester_kurzbz;
nearestSemDiff = 0;
return;
}
let diff = Math.min(Math.abs(now - start), Math.abs(now - end));
if (nearestSem === null || diff < nearestSemDiff) {
nearestSem = sem.studiensemester_kurzbz;
nearestSemDiff = diff;
}
});
return nearestSem;
},
currentIsFirst() {
return this.studiensemester[0].studiensemester_kurzbz == this.currentSemester;
},
currentIsLast() {
return this.studiensemester[this.studiensemester.length-1].studiensemester_kurzbz == this.currentSemester;
}
},
methods: {
clickMode(evt, mode) {
localStorage.setItem('myLvaDefaultMode', mode)
this.mode = mode
},
prevSem() {
this.$refs.studiensemester.selectedIndex--;
this.$refs.studiensemester.dispatchEvent(new Event('change', { bubbles: true }));
},
nextSem() {
this.$refs.studiensemester.selectedIndex++;
this.$refs.studiensemester.dispatchEvent(new Event('change', { bubbles: true }));
},
updateRouter(val) {
this.$router.push(`/Cis/MyLv/${val}`);
}
},
created() {
axios.get(FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/components/Cis/Mylv/Studiensemester').then(res => {
this.studiensemester = res.data.retval || [];
const routerStudiensemester = this.$route.params.studiensemester;
if (routerStudiensemester && this.studiensemester.filter(s => s.studiensemester_kurzbz == routerStudiensemester).length)
this.currentSemester = routerStudiensemester;
else
this.currentSemester = this.nearestSem;
});
},
beforeRouteUpdate(to, from, next){
if (to.params.studiensemester && this.studiensemester.filter(s => s.studiensemester_kurzbz == to.params.studiensemester).length && to.params.studiensemester != this.currentSemester)
this.currentSemester = to.params.studiensemester;
next();
},
template: `
<div>
<h2>{{$p.t('lehre/myLV')}}</h2>
<hr>
<div class="mylv" v-if="ready">
<div v-if="currentSemester" class="row justify-content-center mb-3">
<div class="col-auto d-none">
<label class="col-form-label">{{$p.t('lehre/studiensemester')}}</label>
</div>
<div class="col-auto">
<div class="input-group">
<button :aria-label="$p.t('lehre','previousStudSemester')" v-tooltip.top="{showDelay:1000, value:$p.t('lehre','previousStudSemester')}" class="btn btn-outline-secondary" type="button" :disabled="currentIsFirst" @click="prevSem">
<i class="fa fa-caret-left" aria-hidden="true"></i>
</button>
<select ref="studiensemester" v-model="currentSemester" class="form-select" :aria-label="$p.t('global/studiensemester_auswaehlen')" @change="updateRouter($event.target.value)">
<option v-for="semester in studiensemester" :key="semester.studiensemester_kurzbz">{{semester.studiensemester_kurzbz}}</option>
</select>
<button class="btn btn-outline-secondary" :aria-label="$p.t('lehre','nextStudSemester')" v-tooltip.top="{showDelay:1000, value:$p.t('lehre','nextStudSemester')}" type="button" :disabled="currentIsLast" @click="nextSem">
<i class="fa fa-caret-right" aria-hidden="true"></i>
</button>
</div>
</div>
<div class=" col-auto my-lva-modes">
<div class="d-flex gap-1 justify-content-end" role="group">
<button
type="button"
class="btn btn-outline-secondary"
:class="{active: mode === 'cards'}"
@click="clickMode($event, 'cards')"
>
<i class="fa fa-grip"></i>
</button>
<button
type="button"
class="btn btn-outline-secondary"
:class="{active: mode === 'table'}"
@click="clickMode($event, 'table')"
>
<i class="fa fa-table"></i>
</button>
</div>
</div>
</div>
<div class="alert alert-danger" role="alert" v-else>
{{$p.t('lehre/noLvFound')}}
</div>
<mylv-semester-cards v-if="mode == 'cards'" v-bind="current"/>
<mylv-table v-else-if="mode == 'table'" v-bind="current"/>
</div>
<div class="mylv text-center" v-else>
<i class="fa-solid fa-spinner fa-pulse fa-3x"></i>
</div>
</div>`
};
@@ -1,6 +1,7 @@
import FhcCalendar from "../../Calendar/LvPlan.js";
import ApiLvPlan from '../../../api/factory/lvPlan.js';
import ApiRoomPlan from '../../../api/factory/calendar/roomPlan.js';
export const DEFAULT_MODE_RAUMINFO_MOBILE = 'List';
export const DEFAULT_MODE_RAUMINFO_DESKTOP = 'Week';
@@ -24,6 +25,36 @@ export default {
return this.propsViewData?.mode || defaultMode;
}
},
data() {
return {
filteredGroups: [],
abortController: null,
createContext: {
scope: 'slot_room',
show_all_fields: false,
room_create_information: {
semester: [1, 2, 3, 4, 5, 6, 7, 8],
verband: ['A', 'B', 'C', 'D', 'E', 'F', 'V'],
gruppe: [1, 2, 3, 4],
studiengaenge: [],
searchGroup: this.searchGroup,
searchLektor: this.searchLektor,
},
}
}
},
created() {
this.$api.call(ApiRoomPlan.getRoomCreationInfo())
.then(result => result.data)
.then(result => {
if (result.berechtigt)
{
this.createContext.room_create_information.studiengaenge = result.studiengaenge
}
this.createContext.show_all_fields = result.berechtigt;
});
},
methods:{
handleChangeDate(day, newMode) {
return this.handleChangeMode(newMode, day);
@@ -41,8 +72,81 @@ export default {
}
});
},
async handleCreateEvent(event)
{
event.ort_kurzbz = this.propsViewData.ort_kurzbz;
this.$api.call(ApiRoomPlan.addRoomReservation(event));
this.$refs.calendar.resetEventLoader();
this.$refs.calendar.closeModal();
},
async handleDeleteEvent(event)
{
if (event.type !== 'reservierung')
return;
if (luxon.DateTime.fromISO(`${event.datum}T${event.beginn}`) < luxon.DateTime.now())
return;
this.$api.call(ApiRoomPlan.deleteRoomReservation(event.reservierung_id));
this.$refs.calendar.reset();
},
async searchGroup(event)
{
const query = event.query.trim();
if (query.length < 2)
return [];
if (this.abortController)
this.abortController.abort();
this.abortController = new AbortController();
const signal = this.abortController.signal;
return this.$api.call(ApiRoomPlan.getGruppen(query), { signal })
.then(result => {
return result.data.map(gruppe => ({
label: gruppe.bezeichnung
? `${gruppe.gruppe_kurzbz.trim()} (${gruppe.bezeichnung})`
: gruppe.gruppe_kurzbz.trim(),
gid: gruppe.gid,
gruppe_kurzbz: gruppe.gruppe_kurzbz.trim(),
lehrverband: gruppe.lehrverband,
})
);
})
.catch((e)=> {
this.$fhcAlert.handleSystemError(e)
return []
})
},
async searchLektor(event)
{
const query = event.query.trim();
if (query.length < 2)
return [];
if (this.abortController)
this.abortController.abort();
this.abortController = new AbortController();
const signal = this.abortController.signal;
return this.$api.call(ApiRoomPlan.getLektor(query), { signal })
.then(result => {
return result.data.map(lektor => ({
label: `${lektor.nachname} ${lektor.vorname} (${lektor.uid})`,
uid: lektor.uid
})
)})
.catch(this.$fhcAlert.handleSystemError)
},
getPromiseFunc(start, end) {
return [
this.$api.call(ApiRoomPlan.getReservableMap(this.propsViewData.ort_kurzbz, start.toISODate(), end.toISODate())),
this.$api.call(ApiLvPlan.getRoomInfo(this.propsViewData.ort_kurzbz, start.toISODate(), end.toISODate())),
this.$api.call(ApiLvPlan.getOrtReservierungen(this.propsViewData.ort_kurzbz, start.toISODate(), end.toISODate()))
];
@@ -57,8 +161,12 @@ export default {
:get-promise-func="getPromiseFunc"
:date="currentDay"
:mode="currentMode"
:reservierbar="true"
:create-context="createContext"
@update:date="handleChangeDate"
@update:mode="handleChangeMode"
@create-event="handleCreateEvent"
@delete-event="handleDeleteEvent"
class="responsive-calendar"
></fhc-calendar>
</div>`
+5 -3
View File
@@ -1,6 +1,7 @@
import MylvSemesterStudiengang from "./Semester/Studiengang.js";
export default {
name: 'Semester',
components: {
MylvSemesterStudiengang
},
@@ -36,10 +37,11 @@ export default {
return this.lvs.filter(lv => lv.studiengang_kz == studiengang.studiengang_kz && lv.semester == studiengang.semester);
}
},
template: `<div class="mylv-semester" v-if="ready">
<mylv-semester-studiengang v-for="studiengang in studiengaenge" :key="studiengang.studiengang_kz" v-bind="studiengang" :lvs="lvsForStudiengang(studiengang)"/>
template: `
<div class="mylv-semester" v-if="ready">
<mylv-semester-studiengang v-for="studiengang in studiengaenge" :key="studiengang.studiengang_kz" v-bind="studiengang" :lvs="lvsForStudiengang(studiengang)" :semesterInfo="$props.semester" />
</div>
<div class="mylv-semester text-center" v-else>
<i class="fa-solid fa-spinner fa-pulse fa-3x"></i>
</div>`
};
};
@@ -2,7 +2,10 @@ import MylvSemesterStudiengangLv from "./Studiengang/Lv.js";
import MylvSemesterStudiengangAverageGrade from "./Studiengang/AverageGrade.js";
import Phrasen from "../../../../mixins/Phrasen.js";
import ApiAuthinfo from '../../../../api/factory/authinfo.js';
export default {
name: 'Studiengang',
components: {
MylvSemesterStudiengangLv,
MylvSemesterStudiengangAverageGrade
@@ -14,9 +17,15 @@ export default {
bezeichnung: String,
kuerzel: String,
semester: [String,Number],
semesterInfo: [String,Number],
lvs: Array,
sg_bezeichnung_eng: String
},
data() {
return {
isAverageGradeDisplayed: false,
}
},
computed: {
lehrveranstaltungen() {
return [... new Map(
@@ -32,9 +41,17 @@ export default {
note(lv) {
return lv.benotung ? lv.znote || lv.lvnote || null : null;
},
async checkIfAverageGradeIsDisplayed() {
const authInfoResponse = await this.$api.call(ApiAuthinfo.getAuthInfo());
const authInfo = authInfoResponse.data;
this.isAverageGradeDisplayed = !!authInfo.isStudent;
},
},
template: `<div class="card mb-3">
created() {
this.checkIfAverageGradeIsDisplayed();
},
template: `
<div class="card mb-3">
<div class="card-body">
<h4 class="card-title mb-3">{{$p.user_language.value === 'English' ? sg_bezeichnung_eng : bezeichnung}} - {{kuerzel}}
<small>{{semester}}.{{$p.t('lehre/semester')}}</small>
@@ -44,7 +61,7 @@ export default {
<mylv-semester-studiengang-lv v-bind="lv" class="text-center h-100"></mylv-semester-studiengang-lv>
</div>
</div>
<mylv-semester-studiengang-average-grade :lvs="lvs" />
<mylv-semester-studiengang-average-grade v-if="isAverageGradeDisplayed" :semesterInfo="$props.semesterInfo" />
</div>
</div>`
};
};
@@ -1,50 +1,42 @@
import Phrasen from "../../../../../mixins/Phrasen.js";
import ApiLehre from "../../../../../api/factory/lehre.js";
export default {
mixins: [
Phrasen
],
props: {
lvs: Array,
semesterInfo: String,
},
data: ( ) =>{
data() {
return {
gradeAverage: null,
gradeWeightedAverage: null,
existingGrades: false
}
},
methods: {
calculateAverages(){
let sum = 0;
let count = 0;
let sumWeighted = 0;
let sumEcts = 0;
async fetchAverageGrade() {
this.gradeAverage = null;
this.gradeWeightedAverage = null;
if (!this.$props.semesterInfo) return;
this.lvs.forEach((lv) => {
if ((lv.znote >= 1 && lv.znote <= 5) && lv.znote!= null) {
this.existingGrades = true;
sum+= lv.znote;
count++;
sumWeighted += lv.znote * Number(lv.ects);
sumEcts += Number(lv.ects);
}
});
this.gradeAverage = (sum/count).toFixed(2);
this.gradeWeightedAverage = (sumWeighted/sumEcts).toFixed(2);
}
let gradeAverageResponse = await this.$api.call(
ApiLehre.getSemesterAverageGrade(this.$props.semesterInfo),
);
const gradeAverageResponseData = gradeAverageResponse.data;
this.gradeAverage =
gradeAverageResponseData.average_grade?.toFixed(2);
this.gradeWeightedAverage =
gradeAverageResponseData.weighted_average_grade?.toFixed(2);
},
},
watch: {
lvs: {
handler() {
this.calculateAverages();
},
deep: true,
immediate: true
}
semesterInfo() {
this.fetchAverageGrade();
},
},
mounted(){
this.calculateAverages();
async created() {
await this.fetchAverageGrade();
},
template: /*html*/`
<div class="card mylv-semester-studiengang-grades">
@@ -53,7 +45,7 @@ export default {
<h6>{{$p.t('lehre/notenstatistik')}}</h6>
</div>
<div v-if="existingGrades">
<div v-if="gradeAverage && gradeWeightedAverage">
<table class="card-body table w-auto mx-auto">
<tbody>
<tr>
@@ -79,7 +71,7 @@ export default {
<p>{{$p.t('lehre/info_noGradesYet')}}</p>
</div>
<div v-if="existingGrades" class="card-footer d-flex align-items-start text-muted small">
<div v-if="gradeAverage && gradeWeightedAverage" class="card-footer d-flex align-items-start text-muted small">
<i class="fa fa-circle-info me-2 mt-1"></i>
<div>
<strong>{{$p.t('ui', 'hinweis')}}</strong><br>
@@ -1,7 +1,4 @@
import LvPruefungen from "./Lv/Pruefungen.js";
import LvInfo from "./Lv/Info.js";
import Phrasen from "../../../../../mixins/Phrasen.js";
import LvUebersicht from "../../LvUebersicht.js";
import ApiLehre from '../../../../../api/factory/lehre.js';
import ApiAddons from '../../../../../api/factory/addons.js';
@@ -9,24 +6,20 @@ import ApiAddons from '../../../../../api/factory/addons.js';
// TODO(chris): L10n
export default {
components:{
LvUebersicht,
},
mixins: [
Phrasen
],
inject: ['studien_semester'],
name: 'Lv',
inject: ['studien_semester', 'type'],
props: {
lehrveranstaltung_id: Number,
lehrveranstaltung_id: [Number, String],
semesterstunden: [Number, String],
bezeichnung: String,
bezeichnung_eng: String,
module: String,
farbe: String,
lvinfo: Boolean,
benotung: Boolean,
lvnote: String,
lvnote: [String, Number],
lvnotebez: Array,
znote: String,
znote: [String, Number],
znotebez: Array,
studiengang_kuerzel: String,
semester: [String, Number],
@@ -35,13 +28,18 @@ export default {
ects: String,
incoming: Number,
positiv: Boolean,
note_index: String
note_index: String,
menu: [Array, String]
},
provide() {
return {
studium_studiensemester: Vue.computed(() => this.studien_semester),
}
},
data: () => {
return {
pruefungenData: null,
info: null,
menu: null,
preselectedMenuItem: null,
}
},
@@ -65,12 +63,6 @@ export default {
emptyMenu(){
return !this.menu || !Array.isArray(this.menu) || Array.isArray(this.menu) && this.menu.length == 0;
},
bodyStyle() {return {};
/*const bodyStyle = {};
if (this.farbe)
bodyStyle['background-color'] = '#' + this.farbe;
return bodyStyle;*/
},
grade() {
const languageIndex = this.$p.user_language.value === 'English' ? 1 : 0
// no more showing of grade LV, if grade Zeugnis is not existing yet
@@ -84,7 +76,6 @@ export default {
},
},
methods: {
fetchMenu(lehrveranstaltung_id = this.lehrveranstaltung_id, studien_semester = this.studien_semester) {
return this.$api
.call(ApiAddons.getLvMenu(lehrveranstaltung_id, studien_semester))
@@ -96,105 +87,84 @@ export default {
this.menu = [];
});
},
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 {
} else {
return menuItem.c4_link ?? null;
}
},
openLvOption(menuItem){
if (menuItem.id == "core_menu_mailanstudierende"){
window.location.href = menuItem.c4_link;
} else if (menuItem.id == "core_menu_digitale_anwesenheitslisten") {
window.location.href = menuItem.c4_link;
} else{
this.preselectedMenuItem = menuItem;
Vue.nextTick(() => {
this.$refs.lvUebersicht.show();
});
}
},
openPruefungen() {
// early return if the pruefungenData is empty or not set
if (!this.LvHasPruefungenInformation) return;
LvPruefungen.popup({
pruefungenData: this.pruefungenData,
bezeichnung: this.bezeichnung
});
},
openInfos() {
if (!this.info) {
this.info = true;
// TODO(chris): load all this params on ajax?
LvInfo.popup({
lehrveranstaltung_id: this.lehrveranstaltung_id,
bezeichnung: this.bezeichnung,
bezeichnung_eng: this.bezeichnung_eng,
studiengang_kuerzel: this.studiengang_kuerzel,
semester: this.semester,
studien_semester: this.studien_semester,
orgform_kurzbz: this.orgform_kurzbz,
sprache: this.sprache,
ects: this.ects,
incoming: this.incoming
}).then(() => this.info = false).catch(() => this.info = false);
}
}
},
watch:{
studien_semester(newValue){
this.fetchMenu(this.lehrveranstaltung_id, newValue);
}
},
created() {
this.$api
.call(ApiLehre.getStudentPruefungen(this.lehrveranstaltung_id))
.then(res => res.data)
.then(pruefungen => {
this.pruefungenData = pruefungen;
});
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);
},
template: /*html*/`<div class="mylv-semester-studiengang-lv card">
<lv-uebersicht ref="lvUebersicht" :preselectedMenu="preselectedMenuItem" :event="{
lehrveranstaltung_id: lehrveranstaltung_id,
studiensemester_kurzbz:studien_semester,
lehrfach_bez:studien_semester,
stg_kurzbzlang:studien_semester,
}"/>
template: /*html*/`
<div class="mylv-semester-studiengang-lv card">
<div class="p-2" :class="is_organisatorische_einheit?'':'card-header'">
<!-- {{module}} if the module of the lv is important then query the module from the api endpoint for LV-->
<h6 class="fw-bold" v-if="is_organisatorische_einheit" >{{ $p.t('lehre/organisationseinheit') }}:</h6>
<h6 class="mb-0">{{$p.user_language.value === 'English' ? bezeichnung_eng : bezeichnung}}</h6>
</div>
<div v-if="!emptyMenu" class="card-body " :style="bodyStyle">
<div v-if="!emptyMenu" class="card-body ">
<template v-if="menu">
<ul class="list-group border-top-0 border-bottom-0 rounded-0">
<li :type="menuItem.c4_link ? 'button' : null" v-for="menuItem in menu" class="list-group-item border-0 " >
<div class="d-flex flex-row" :data-bs-toggle="menuItem.c4_moodle_links?.length ? 'dropdown' : null">
<li :type="menuItem.c4_link ? 'button' : null"
v-for="(menuItem, index) in menu" :key="index" class="list-group-item border-0 " >
<div class="d-flex flex-row">
<div class="mx-4">
<i :class="[menuItem.c4_icon2 ? menuItem.c4_icon2 : 'fa-solid fa-pen-to-square', !menuItem.c4_link ? 'unavailable' : null ]"></i>
</div>
<a
<a :id="menuItem.name"
class="fhc-body text-decoration-none text-truncate"
:id="'moodle_links_'+lehrveranstaltung_id"
:class="{ 'unavailable':!menuItem.c4_link, 'dropdown-toggle':menuItem.c4_moodle_links?.length }"
:class="{ 'unavailable':!menuItem.c4_link }"
:target="menuItem.c4_target"
:href="c4_link(menuItem) ? c4_link(menuItem) : null">
{{ menuItem.phrase ? $p.t(menuItem.phrase) : menuItem.name}}
</a>
<div v-if="(menuItem.c4_moodle_links?.length || menuItem.c4_linkList?.length) && menuItem.c4_link" class="dropdown">
<button
class="btn btn-sm dropdown-toggle dropdown-toggle-split border-0"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul v-if="menuItem.c4_moodle_links?.length" class="dropdown-menu dropdown-menu p-0">
<li v-for="item in menuItem.c4_moodle_links" :key="item.url">
<a class="dropdown-item border-bottom" :href="item.url" target="#">{{ item.lehrform }}</a>
</li>
</ul>
<ul v-else class="dropdown-menu dropdown-menu p-0">
<li v-for="([text, link], i) in menuItem.c4_linkList" :key="i">
<a class="dropdown-item border-bottom" :href="link" target="#">{{ text }}</a>
</li>
</ul>
</div>
<ul v-if="menuItem.c4_moodle_links?.length" class="dropdown-menu p-0" :aria-labelledby="'moodle_links_'+lehrveranstaltung_id">
<li v-for="item in menuItem.c4_moodle_links"><a class="dropdown-item border-bottom" :href="item.url">{{item.lehrform}}</a></li>
</ul>
</div>
</li>
</ul>
</template>
@@ -204,13 +174,14 @@ export default {
</div>
</template>
</div>
<div v-if="!emptyMenu" class="card-footer">
<div v-if="!emptyMenu && type == 'student'" class="card-footer">
<div class="row">
<!-- template for the LV if there are multiple pruefungen -->
<template v-if="LvHasPruefungenInformation">
<a href="#" class="col-auto text-start text-decoration-none" @click.prevent="openPruefungen">
<i class="fa fa-check text-success" v-if="positiv"></i>
<span class="ps-1" :style="'color:'+gradeColor">{{ grade || $p.t('lehre/noGrades') }}</span>
<i class="fa fa-circle-info ms-1"></i>
</a>
</template>
<!-- template for the LV with no pruefungen -->
@@ -222,5 +193,12 @@ export default {
</template>
</div>
</div>
<div v-else-if="!emptyMenu && type == 'employee'" class="card-footer">
<div class="row">
<div class="col-auto">
<span class="ps-1">{{ $p.t('lehre/semesterstunden') }}: {{ semesterstunden }}</span>
</div>
</div>
</div>
</div>`
};
};
-129
View File
@@ -1,129 +0,0 @@
import MylvSemester from "./Semester.js";
import Phrasen from "../../../mixins/Phrasen.js";
// TODO(chris): phrase: global/studiensemester_auswaehlen
// TODO(chris): phrase: next & prev +aria-label
export default {
components: {
MylvSemester
},
mixins: [
Phrasen
],
data: () => {
return {
firstLoad: true,
studiensemester: null,
lvs: {},
currentSemester: null
};
},
computed: {
ready() {
return this.studiensemester !== null && (!this.firstLoad || this.current.lvs !== null);
},
current() {
if (this.currentSemester === null)
return { semester: null, lvs: [] };
if (this.lvs[this.currentSemester] === undefined) {
this.lvs[this.currentSemester] = {
semester: this.currentSemester,
lvs: null
};
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;
});
}
return this.lvs[this.currentSemester];
},
nearestSem() {
let now = Date.now();
let nearestSem = null;
let nearestSemDiff = 0;
this.studiensemester.forEach(sem => {
let start = new Date(sem.start);
let end = new Date(sem.ende);
if (now >= start && now <= end) {
nearestSem = sem.studiensemester_kurzbz;
nearestSemDiff = 0;
return;
}
let diff = Math.min(Math.abs(now - start), Math.abs(now - end));
if (nearestSem === null || diff < nearestSemDiff) {
nearestSem = sem.studiensemester_kurzbz;
nearestSemDiff = diff;
}
});
return nearestSem;
},
currentIsFirst() {
return this.studiensemester[0].studiensemester_kurzbz == this.currentSemester;
},
currentIsLast() {
return this.studiensemester[this.studiensemester.length-1].studiensemester_kurzbz == this.currentSemester;
}
},
methods: {
prevSem() {
this.$refs.studiensemester.selectedIndex--;
this.$refs.studiensemester.dispatchEvent(new Event('change', { bubbles: true }));
},
nextSem() {
this.$refs.studiensemester.selectedIndex++;
this.$refs.studiensemester.dispatchEvent(new Event('change', { bubbles: true }));
},
updateRouter(val) {
this.$router.push(`/Cis/MyLv/${val}`);
}
},
created() {
axios.get(FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/components/Cis/Mylv/Studiensemester').then(res => {
this.studiensemester = res.data.retval || [];
const routerStudiensemester = this.$route.params.studiensemester;
if (routerStudiensemester && this.studiensemester.filter(s => s.studiensemester_kurzbz == routerStudiensemester).length)
this.currentSemester = routerStudiensemester;
else
this.currentSemester = this.nearestSem;
});
},
beforeRouteUpdate(to, from, next){
if (to.params.studiensemester && this.studiensemester.filter(s => s.studiensemester_kurzbz == to.params.studiensemester).length && to.params.studiensemester != this.currentSemester)
this.currentSemester = to.params.studiensemester;
next();
},
template: `
<h2>{{$p.t('lehre/myLV')}}</h2>
<hr>
<div class="mylv-student" v-if="ready">
<div v-if="currentSemester" class="row justify-content-center mb-3">
<div class="col-auto d-none">
<label class="col-form-label">{{$p.t('lehre/studiensemester')}}</label>
</div>
<div class="col-auto">
<div class="input-group">
<button :aria-label="$p.t('lehre','previousStudSemester')" v-tooltip.top="{showDelay:1000, value:$p.t('lehre','previousStudSemester')}" class="btn btn-outline-secondary" type="button" :disabled="currentIsFirst" @click="prevSem">
<i class="fa fa-caret-left" aria-hidden="true"></i>
</button>
<select ref="studiensemester" v-model="currentSemester" class="form-select" :aria-label="$p.t('global/studiensemester_auswaehlen')" @change="updateRouter($event.target.value)">
<option v-for="semester in studiensemester" :key="semester.studiensemester_kurzbz">{{semester.studiensemester_kurzbz}}</option>
</select>
<button class="btn btn-outline-secondary" :aria-label="$p.t('lehre','nextStudSemester')" v-tooltip.top="{showDelay:1000, value:$p.t('lehre','nextStudSemester')}" type="button" :disabled="currentIsLast" @click="nextSem">
<i class="fa fa-caret-right" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
<div class="alert alert-danger" role="alert" v-else>
{{$p.t('lehre/noLvFound')}}
</div>
<mylv-semester v-bind="current"/>
</div>
<div class="mylv-student text-center" v-else>
<i class="fa-solid fa-spinner fa-pulse fa-3x"></i>
</div>`
};
+345
View File
@@ -0,0 +1,345 @@
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: null,
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.$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: [
]
}
},
computed: {
ready() { return this.lvs !== null; },
},
methods: {
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
return ''
},
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(cell) {
let container = document.createElement('div');
container.className = "d-flex gap-2";
const data = cell.getData()
if(data.menu && data.menu.length) {
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;
if (hasDropdown) {
// button group
const group = document.createElement('div');
group.className = 'btn-group';
// main action button
const button= this.createActionButton(lvLink)
// 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 = '<span class="visually-hidden">Toggle Dropdown</span>';
// dropdown menu
const dropMenu = document.createElement('ul');
dropMenu.className = 'dropdown-menu dropdown-menu p-0';
// 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 }));
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 {
container.appendChild(this.createActionButton(lvLink));
}
})
}
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 = `<i class="${icon}"></i><span style="margin-left:2px;">${label}</span>`;
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() {
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 - 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)
}
}
},
created() {
this.phrasenPromise = this.$p.loadCategory(['global', 'lehre', 'lvinfo'])
this.phrasenPromise.then(()=> {this.phrasenResolved = true})
},
mounted() {
this.setupMounted()
},
watch: {
lvs: {
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) {
this.$refs.mylvTable.$refs.table.style.setProperty('height', h+'px')
}
},
deep: true
}
},
template: `
<div class="mylv-semester-table" v-if="ready">
<core-filter-cmpt
v-if="phrasenResolved"
@uuidDefined="handleUuidDefined"
:title="''"
ref="mylvTable"
:tabulator-options="mylvTableOptions"
:tabulator-events="mylvTableEventHandlers"
@tableBuilt="handleTableBuilt"
tableOnly
:sideMenu="false"
/>
</div>
<div v-if="tabulatorUuid === null" class="text-center d-flex justify-content-center align-items-center h-100" >
<i class="fa-solid fa-spinner fa-pulse fa-3x"></i>
</div>
`
};
@@ -36,7 +36,7 @@ export default {
return {
showModal: false,
editDataFilter: null,
preloadedPhrasen:{},
arePhrasesPreloaded: false,
// tabulator options
funktionen_table_options: {
persistenceID: "filterTableMaProfilFunktionen",
@@ -48,6 +48,7 @@ export default {
responsiveLayout: "collapse",
responsiveLayoutCollapseUseFormatters: false,
responsiveLayoutCollapseFormatter: Vue.$collapseFormatter,
responsiveLayoutCollapseStartOpen: false,
columns: [
{
title:
@@ -58,24 +59,27 @@ export default {
formatter: "responsiveCollapse",
maxWidth: 40,
headerClick: this.collapseFunction,
visible: true
visible: true,
responsive: 0,
},
{
title: Vue.computed(() => this.preloadedPhrasen.bezeichnungPhrase),
title: Vue.computed(() => this.$p.t('ui/bezeichnung')),
field: "Bezeichnung",
headerFilter: true,
minWidth: 200,
visible: true
visible: true,
responsive: 0,
},
{
title: Vue.computed(() => this.preloadedPhrasen.organisationseinheitPhrase),
title: Vue.computed(() => this.$p.t('lehre/organisationseinheit')),
field: "Organisationseinheit",
headerFilter: true,
minWidth: 200,
visible: true
visible: true,
responsive: 1,
},
{
title: Vue.computed(() => this.preloadedPhrasen.gueltigVonPhrase),
title: Vue.computed(() => this.$p.t('global/gueltigVon')),
field: "Gültig_von",
headerFilterFunc: 'dates',
headerFilter: dateFilter,
@@ -83,10 +87,11 @@ export default {
minWidth: 200,
visible: true,
formatter:"datetime",
formatterParams: this.datetimeFormatterParams()
formatterParams: this.datetimeFormatterParams(),
responsive: 4,
},
{
title: Vue.computed(() => this.preloadedPhrasen.gueltigBisPhrase),
title: Vue.computed(() => this.$p.t('global/gueltigBis')),
field: "Gültig_bis",
headerFilterFunc: 'dates',
headerFilter: dateFilter,
@@ -94,14 +99,16 @@ export default {
minWidth: 200,
visible: true,
formatter:"datetime",
formatterParams: this.datetimeFormatterParams()
formatterParams: this.datetimeFormatterParams(),
responsive: 3,
},
{
title: Vue.computed(() => this.preloadedPhrasen.wochenstundenPhrase),
title: Vue.computed(() => this.$p.t('profil/wochenstunden')),
field: "Wochenstunden",
headerFilter: true,
minWidth: 200,
visible: true
visible: true,
responsive: 2,
},
],
},
@@ -116,6 +123,7 @@ export default {
responsiveLayoutCollapseUseFormatters: false,
responsiveLayoutCollapseFormatter: Vue.$collapseFormatter,
data: [{betriebsmittel: "", Nummer: "", Ausgegeben_am: ""}],
responsiveLayoutCollapseStartOpen: false,
columns: [
{
title:
@@ -126,32 +134,36 @@ export default {
formatter: "responsiveCollapse",
maxWidth: 40,
headerClick: this.collapseFunction,
visible: true
visible: true,
responsive: 0,
},
{
title: Vue.computed(() => this.preloadedPhrasen.entlehnteBetriebsmittelPhrase),
title: Vue.computed(() => this.$p.t('profil/entlehnteBetriebsmittel')),
field: "betriebsmittel",
headerFilter: true,
minWidth: 200,
visible: true
visible: true,
responsive: 0,
},
{
title: Vue.computed(() => this.preloadedPhrasen.inventarnummerPhrase),
title: Vue.computed(() => this.$p.t('profil/inventarnummer')),
field: "Nummer",
headerFilter: true,
resizable: true,
minWidth: 200,
visible: true
visible: true,
responsive: 2,
},
{
title: Vue.computed(() => this.preloadedPhrasen.ausgabedatumPhrase),
title: Vue.computed(() => this.$p.t('profil/ausgabedatum')),
field: "Ausgegeben_am",
headerFilterFunc: 'dates',
headerFilter: dateFilter,
minWidth: 200,
visible: true,
formatter:"datetime",
formatterParams: this.datetimeFormatterParams()
formatterParams: this.datetimeFormatterParams(),
responsive: 1,
},
],
}
@@ -166,11 +178,9 @@ export default {
methods: {
betriebsmittelTableBuilt: function () {
this.$refs.betriebsmittelTable.tabulator.setColumns(this.betriebsmittel_table_options.columns)
this.$refs.betriebsmittelTable.tabulator.setData(this.data.mittel);
},
funktionenTableBuilt: function () {
this.$refs.funktionenTable.tabulator.setColumns(this.funktionen_table_options.columns)
this.$refs.funktionenTable.tabulator.setData(this.data.funktionen);
},
hideEditProfilModal: function () {
@@ -221,8 +231,8 @@ export default {
});
},
setTableColumnTitles() { // reevaluates computed phrasen
if(this.$refs.betriebsmittelTable) this.$refs.betriebsmittelTable.tabulator.setColumns(this.betriebsmittel_table_options.columns)
if(this.$refs.funktionenTable) this.$refs.funktionenTable.tabulator.setColumns(this.funktionen_table_options.columns)
if(this.$refs.betriebsmittelTable) this.$refs.betriebsmittelTable.tabulator.setColumns(this.betriebsmittel_table_options.columns);
if(this.$refs.funktionenTable) this.$refs.funktionenTable.tabulator.setColumns(this.funktionen_table_options.columns);
},
datetimeFormatterParams: function() {
const params = {
@@ -308,15 +318,7 @@ export default {
created() {
// preload phrasen
this.$p.loadCategory(["ui","lehre","global","profil"]).then(() => {
this.preloadedPhrasen.bezeichnungPhrase = this.$p.t('ui/bezeichnung');
this.preloadedPhrasen.organisationseinheitPhrase = this.$p.t('lehre/organisationseinheit');
this.preloadedPhrasen.gueltigVonPhrase = this.$p.t('global/gueltigVon');
this.preloadedPhrasen.gueltigBisPhrase = this.$p.t('global/gueltigBis');
this.preloadedPhrasen.wochenstundenPhrase = this.$p.t('profil/wochenstunden');
this.preloadedPhrasen.entlehnteBetriebsmittelPhrase = this.$p.t('profil/entlehnteBetriebsmittel');
this.preloadedPhrasen.inventarnummerPhrase = this.$p.t('profil/inventarnummer');
this.preloadedPhrasen.ausgabedatumPhrase = this.$p.t('profil/ausgabedatum');
this.preloadedPhrasen.loaded=true;
this.arePhrasesPreloaded = true;
});
//? sorts the profil Updates: pending -> accepted -> rejected
this.data.profilUpdates?.sort(this.sortProfilUpdates);
@@ -336,8 +338,8 @@ export default {
<div class="container-fluid text-break fhc-form" >
<edit-profil v-if="showModal" ref="editModal" :isMitarbeiter="true" @hideBsModal="hideEditProfilModal" :value="JSON.parse(JSON.stringify(filteredEditData))" :titel="$p.t('profil','profilBearbeiten')"></edit-profil>
<div class="row">
<div class="d-md-none col-12 ">
<!-- Bearbeiten Button -->
<div class="d-md-none col-12">
<!-- Bearbeiten Button -->
<div v-if="isEditable" class="row mb-3 ">
<div class="col">
<button @click="()=>showEditProfilModal()" type="button" class="text-start card w-100 btn btn-outline-secondary" >
@@ -350,9 +352,9 @@ export default {
</button>
</div>
</div>
<!-- MOBILE PROFIL UPDATES -->
<div v-if="data.profilUpdates" class="row mb-3">
<div class="col">
<!-- MOBILE PROFIL UPDATES -->
<fetch-profil-updates v-if="data.profilUpdates && data.profilUpdates.length" @fetchUpdates="fetchProfilUpdates" :data="data.profilUpdates" ></fetch-profil-updates>
</div>
</div>
@@ -365,30 +367,42 @@ export default {
<!-- ROW WITH THE PROFIL INFORMATION -->
<div class="row mb-4">
<div class="col-lg-12 col-xl-6 ">
<!-- PROFIL INFORMATION -->
<div class="row mb-4">
<div class="col">
<!-- PROFIL INFORMATION -->
<profil-information @showEditProfilModal="showEditProfilModal" :title="$p.t('profil','mitarbeiterIn')" :data="profilInformation" :fotoStatus="fotoStatus"></profil-information>
</div>
</div>
<!-- QUICK LINKS, MOBILE VIEW (HIDDEN IF VIEWPORT >= MD BREAKPOINT) -->
<div v-if="quickLinks.length" class="row mb-4 d-md-none">
<div class="col">
<quick-links :title="$p.t('profil/quickLinks')" :links="quickLinks" />
</div>
</div>
<!-- CALENDAR SYNC OPTIONS, MOBILE VIEW (HIDDEN IF VIEWPORT >= MD BREAKPOINT) -->
<div class="row mb-4 d-md-none">
<div class="col">
<calendar-sync :uid="$props.data.username" :calendarSyncUrls="$props.calendarSyncUrls"></calendar-sync>
</div>
</div>
<!-- MITARBEITER INFO -->
<div class="row mb-4">
<div class=" col-lg-12">
<!-- MITARBEITER INFO -->
<role-information :title="$p.t('profil','mitarbeiterInformation')" :data="roleInformation"></role-information>
</div>
</div>
<!-- START OF SECOND PROFIL INFORMATION COLUMN -->
</div>
<div class="col-xl-6 col-lg-12 ">
<!-- EMAILS -->
<div class="row mb-4">
<div class="col">
<!-- EMAILS -->
<profil-emails :title="this.$p.t('person','email')" :data="data.emails" ></profil-emails>
</div>
</div>
<!-- PRIVATE KONTAKTE-->
<div class="row mb-4 ">
<div class="col">
<!-- PRIVATE KONTAKTE-->
<div class="card">
<div class="card-header">
<div class="row">
@@ -410,9 +424,9 @@ export default {
</div>
</div>
</div>
<!-- PRIVATE ADRESSEN-->
<div class="row mb-4">
<div class="col">
<!-- PRIVATE ADRESSEN-->
<div class="card">
<div class="card-header">
<div class="row">
@@ -437,10 +451,10 @@ export default {
</div>
</div >
<div class="row">
<!-- FUNKTIONEN TABELLE -->
<div class="col-12 mb-4" >
<!-- FUNKTIONEN TABELLE -->
<core-filter-cmpt
v-if="preloadedPhrasen.loaded"
v-if="arePhrasesPreloaded"
@tableBuilt="funktionenTableBuilt"
:title="$p.t('person','funktionen')"
ref="funktionenTable"
@@ -449,10 +463,10 @@ export default {
:sideMenu="false"
/>
</div>
<!-- BETRIEBSMITTEL TABELLE -->
<div class="col-12 mb-4" >
<!-- BETRIEBSMITTEL TABELLE -->
<core-filter-cmpt
v-if="preloadedPhrasen.loaded"
v-if="arePhrasesPreloaded"
@tableBuilt="betriebsmittelTableBuilt"
:title="$p.t('profil','entlehnteBetriebsmittel')"
ref="betriebsmittelTable"
@@ -465,13 +479,8 @@ export default {
</div>
<!-- START OF SIDE PANEL -->
<div class="col-md-4 col-xxl-3 col-sm-12 text-break" >
<div v-if="quickLinks.length" class="row mb-4">
<div class="col">
<quick-links :title="$p.t('profil/quickLinks')" :links="quickLinks" />
</div>
</div>
<!-- Bearbeiten Button -->
<div class="row d-none d-md-block ">
<div v-if="isEditable" class="row d-none d-md-block ">
<div class="col mb-3">
<button @click="()=>showEditProfilModal()" type="button" class="text-start card w-100 btn btn-outline-secondary" >
<div class="row">
@@ -483,29 +492,36 @@ export default {
</button>
</div>
</div>
<div v-if="data.profilUpdates" class="row d-none d-md-block mb-3">
<div class="col mb-3">
<!-- PROFIL UPDATES -->
<fetch-profil-updates v-if="data.profilUpdates && data.profilUpdates.length" @fetchUpdates="fetchProfilUpdates" :data="data.profilUpdates"></fetch-profil-updates>
</div>
</div>
<div class="row mb-3" >
<div class="col-12">
<!-- AUSWEIS STATUS -->
<ausweis-status :data="data.zutrittsdatum"></ausweis-status>
</div>
</div>
<div class="row mb-3">
<div class="col">
<!-- MAILVERTEILER -->
<mailverteiler :data="data?.mailverteiler" :title="$p.t('profil','mailverteiler')"></mailverteiler>
</div>
</div>
<div class="row">
<!-- QUICK LINKS, HIDDEN IF VIEWPORT < MD BREAKPOINT -->
<div v-if="quickLinks.length" class="row mb-3 d-none d-md-block">
<div class="col">
<quick-links :title="$p.t('profil/quickLinks')" :links="quickLinks" />
</div>
</div>
<!-- CALENDAR SYNC OPTIONS, HIDDEN IF VIEWPORT < MD BREAKPOINT -->
<div class="row mb-3 d-none d-md-block">
<div class="col">
<calendar-sync :uid="$props.data.username" :calendarSyncUrls="$props.calendarSyncUrls"></calendar-sync>
</div>
</div>
<!-- PROFIL UPDATES -->
<div v-if="data.profilUpdates" class="row d-none d-md-block mb-3">
<div class="col mb-3">
<fetch-profil-updates v-if="data.profilUpdates && data.profilUpdates.length" @fetchUpdates="fetchProfilUpdates" :data="data.profilUpdates"></fetch-profil-updates>
</div>
</div>
<!-- AUSWEIS STATUS -->
<div class="row mb-3" >
<div class="col-12">
<ausweis-status :data="data.zutrittsdatum"></ausweis-status>
</div>
</div>
<!-- MAILVERTEILER -->
<div class="row mb-3">
<div class="col">
<mailverteiler :data="data?.mailverteiler" :title="$p.t('profil','mailverteiler')"></mailverteiler>
</div>
</div>
</div>
</div>
</div>
@@ -20,7 +20,7 @@ export default {
data() {
return {
collapseIconFunktionen: true,
preloadedPhrasen: {},
arePhrasesPreloaded: false,
funktionen_table_options: {
persistenceID: "filterTableMaViewProfilFunktionen",
persistence: {
@@ -43,6 +43,7 @@ export default {
maxWidth: 40,
headerClick: this.collapseFunction,
visible: true,
responsive: 0,
},
{
title: Vue.computed(() => this.$p.t("ui/bezeichnung")),
@@ -50,6 +51,7 @@ export default {
headerFilter: true,
minWidth: 200,
visible: true,
responsive: 0,
},
{
title: Vue.computed(() =>
@@ -59,6 +61,7 @@ export default {
headerFilter: true,
minWidth: 200,
visible: true,
responsive: 1,
},
{
title: Vue.computed(() =>
@@ -72,6 +75,7 @@ export default {
visible: true,
formatter: "datetime",
formatterParams: this.datetimeFormatterParams(),
responsive: 4,
},
{
title: Vue.computed(() =>
@@ -85,6 +89,7 @@ export default {
visible: true,
formatter: "datetime",
formatterParams: this.datetimeFormatterParams(),
responsive: 3,
},
{
title: Vue.computed(() =>
@@ -94,6 +99,7 @@ export default {
headerFilter: true,
minWidth: 200,
visible: true,
responsive: 2,
},
],
},
@@ -216,19 +222,7 @@ export default {
},
created() {
this.$p.loadCategory(["ui", "lehre", "global", "profil"]).then(() => {
this.preloadedPhrasen.bezeichnungPhrase =
this.$p.t("ui/bezeichnung");
this.preloadedPhrasen.organisationseinheitPhrase = this.$p.t(
"lehre/organisationseinheit",
);
this.preloadedPhrasen.gueltigVonPhrase =
this.$p.t("global/gueltigVon");
this.preloadedPhrasen.gueltigBisPhrase =
this.$p.t("global/gueltigBis");
this.preloadedPhrasen.wochenstundenPhrase = this.$p.t(
"profil/wochenstunden",
);
this.preloadedPhrasen.loaded = true;
this.arePhrasesPreloaded = true;
});
},
@@ -237,15 +231,6 @@ export default {
<div class="container-fluid text-break fhc-form" >
<!-- ROW -->
<div class="row">
<!-- HIDDEN QUICK LINKS -->
<!-- TODO: uncomment when implemented
<div class="d-md-none col-12 ">
<quick-links :title="$p.t('profil','quickLinks')" :mobile="true" ></quick-links>
</div>
-->
<!-- END OF HIDDEN QUCK LINKS -->
<!-- MAIN PANNEL -->
<div class="col-sm-12 col-md-8 col-xxl-9 ">
<!-- ROW WITH PROFIL IMAGE AND INFORMATION -->
@@ -254,27 +239,33 @@ export default {
<div class="row mb-4">
<!-- FIRST KAESTCHEN -->
<div class="col-lg-12 col-xl-6 ">
<!-- Profil Informationen -->
<div class="row mb-4">
<div class="col">
<!-- Profil Informationen -->
<profil-information :title="$p.t('profil','mitarbeiterIn')" :data="profilInformation" :fotoStatus="fotoStatus"></profil-information>
</div>
</div>
<!-- QUICK LINKS, MOBILE VIEW (HIDDEN IF VIEWPORT >= MD BREAKPOINT) -->
<div v-if="quickLinks.length" class="row mb-4 d-md-none">
<div class="col">
<quick-links :title="$p.t('profil/quickLinks')" :links="quickLinks" />
</div>
</div>
<!-- START OF SECOND PROFIL INFORMATION COLUMN -->
<!-- END OF PROFIL INFORMATION ROW -->
<!-- INFORMATION CONTENT END -->
</div>
<div class="col-xl-6 col-lg-12 ">
<!-- EMAILS -->
<div class="row mb-4">
<div class="col">
<!-- EMAILS -->
<profil-emails :title="this.$p.t('person','email')" :data="personEmails"></profil-emails>
</div>
</div>
<!-- SECOND ROW OF SECOND COLUMN IN MAIN CONTENT -->
<!-- roleInformation -->
<div class="row mb-4">
<div class=" col-lg-12">
<!-- roleInformation -->
<role-information :data="roleInformation" :title="$p.t('profil','mitarbeiterInformation')"></role-information>
</div>
</div>
@@ -288,7 +279,7 @@ export default {
<div class="row">
<!-- FIRST TABLE -->
<div class="col-12 mb-4" >
<core-filter-cmpt v-if="preloadedPhrasen.loaded" @tableBuilt="funktionenTableBuilt" :title="$p.t('person','funktionen')" ref="funktionenTable" :tabulator-options="funktionen_table_options" tableOnly :sideMenu="false" />
<core-filter-cmpt v-if="arePhrasesPreloaded" @tableBuilt="funktionenTableBuilt" :title="$p.t('person','funktionen')" ref="funktionenTable" :tabulator-options="funktionen_table_options" tableOnly :sideMenu="false" />
</div>
<!-- END OF THE ROW WITH THE TABLES UNDER THE PROFIL INFORMATION -->
</div>
@@ -296,15 +287,15 @@ export default {
</div>
<!-- START OF SIDE PANEL -->
<div class="col-md-4 col-xxl-3 col-sm-12 text-break" >
<!-- VISIBLE UNTIL VIEWPORT MD -->
<div v-if="quickLinks.length" class="row mb-4">
<!-- QUICK LINKS, HIDDEN IF VIEWPORT < MD BREAKPOINT -->
<div v-if="quickLinks.length" class="row mb-3 d-none d-md-block">
<div class="col">
<quick-links :title="$p.t('profil/quickLinks')" :links="quickLinks" />
</div>
</div>
<!-- MAILVERTEILER -->
<div class="row">
<div class="col">
<!-- MAILVERTEILER -->
<mailverteiler :data="data?.mailverteiler" :title="$p.t('profil','mailverteiler')"></mailverteiler>
</div>
</div>
+1 -1
View File
@@ -171,8 +171,8 @@ export const Profil = {
const data = viewDataResult.data;
if (!data) return;
this.isEditable = data.editable;
this.view = data.profil_data.view;
this.isEditable = data.profil_data.editable;
this.data = data.profil_data.data;
this.calendarSyncUrls = data.calendar_sync_urls ?? [];
this.authPermissions = data.permissions;
@@ -35,7 +35,7 @@ export default {
showModal: false,
collapseIconBetriebsmittel: true,
editDataFilter: null,
preloadedPhrasen:{},
arePhrasesPreloaded: false,
// tabulator options
zutrittsgruppen_table_options: {
persistenceID: "filterTableStudentProfilZutrittsgruppen",
@@ -44,10 +44,12 @@ export default {
},
minHeight: 200,
layout: "fitColumns",
columns: [{
title: Vue.computed(() => this.preloadedPhrasen.zutrittsGruppenPhrase),
columns: [
{
title: Vue.computed(() => this.$p.t('profil/zutrittsGruppen')),
field: "bezeichnung"
}],
}
],
},
betriebsmittel_table_options: {
persistenceID: "filterTableStudentProfilBetriebsmittel",
@@ -59,6 +61,7 @@ export default {
responsiveLayout: "collapse",
responsiveLayoutCollapseUseFormatters: false,
responsiveLayoutCollapseFormatter: Vue.$collapseFormatter,
responsiveLayoutCollapseStartOpen: false,
columns: [
{
title:
@@ -69,31 +72,35 @@ export default {
formatter: "responsiveCollapse",
maxWidth: 40,
headerClick: this.collapseFunction,
responsive: 0,
},
{
title: Vue.computed(()=>this.preloadedPhrasen.entlehnteBetriebsmittelPhrase),
title: Vue.computed(()=>this.$p.t('profil/entlehnteBetriebsmittel')),
field: "betriebsmittel",
headerFilter: true,
minWidth: 200,
visible: true
visible: true,
responsive: 0,
},
{
title: Vue.computed(() =>this.preloadedPhrasen.inventarnummerPhrase) ,
title: Vue.computed(() => this.$p.t('profil/inventarnummer')) ,
field: "Nummer",
headerFilter: true,
resizable: true,
minWidth: 200,
visible: true
visible: true,
responsive: 2,
},
{
title: Vue.computed(() =>this.preloadedPhrasen.ausgabedatum) ,
title: Vue.computed(() => this.$p.t('profil/ausgabedatum')) ,
field: "Ausgegeben_am",
headerFilterFunc: 'dates',
headerFilter: dateFilter,
minWidth: 200,
visible: true,
formatter:"datetime",
formatterParams: this.datetimeFormatterParams()
formatterParams: this.datetimeFormatterParams(),
responsive: 1,
},
],
},
@@ -113,11 +120,9 @@ export default {
methods: {
betriebsmittelTableBuilt: function () {
this.$refs.betriebsmittelTable.tabulator.setColumns(this.betriebsmittel_table_options.columns)
this.$refs.betriebsmittelTable.tabulator.setData(this.data.mittel);
},
zutrittsgruppenTableBuilt: function () {
this.$refs.zutrittsgruppenTable.tabulator.setColumns(this.zutrittsgruppen_table_options.columns)
this.$refs.zutrittsgruppenTable.tabulator.setData(
this.data.zuttritsgruppen
);
@@ -225,6 +230,10 @@ export default {
label: `${this.$p.t('person','personenkennzeichen')}`,
value: this.data.personenkennzeichen
},
matrikelnummer: {
label: this.$p.t('person/matrikelnummer'),
value: this.data.matrikelnummer
},
studiengang: {
label: `${this.$p.t('lehre','studiengang')}`,
value: this.data.studiengang
@@ -252,11 +261,7 @@ export default {
created() {
// preload phrasen
this.$p.loadCategory('profil').then(() => {
this.preloadedPhrasen.zutrittsGruppenPhrase = this.$p.t('profil/zutrittsGruppen');
this.preloadedPhrasen.entlehnteBetriebsmittelPhrase = this.$p.t('profil/entlehnteBetriebsmittel');
this.preloadedPhrasen.inventarnummerPhrase = this.$p.t('profil/inventarnummer');
this.preloadedPhrasen.ausgabedatum = this.$p.t('profil/ausgabedatum');
this.preloadedPhrasen.loaded = true;
this.arePhrasesPreloaded = true;
});
//? sorts the profil Updates: pending -> accepted -> rejected
this.data.profilUpdates?.sort(this.sortProfilUpdates);
@@ -275,26 +280,25 @@ export default {
<div class="row">
<div class="d-md-none col-12 ">
<!-- Bearbeiten Button -->
<div v-if="isEditable" class="row ">
<div class="col mb-3">
<div v-if="isEditable" class="row">
<div class="col mb-4">
<button @click="showEditProfilModal" type="button" class="card text-start w-100 btn btn-outline-secondary" >
<div class="row">
<div class="col-2">
<div class="col-auto">
<i class="fa fa-edit"></i>
</div>
<div class="col-10">{{$p.t('ui','bearbeiten')}}</div>
<div class="col-auto">{{$p.t('ui','bearbeiten')}}</div>
</div>
</button>
</div>
</div>
<!-- MOBILE PROFIL UPDATES -->
<div v-if="data.profilUpdates" class="row mb-3">
<div class="col">
<!-- MOBILE PROFIL UPDATES -->
<fetch-profil-updates v-if="data.profilUpdates && data.profilUpdates.length" @fetchUpdates="fetchProfilUpdates" :data="data.profilUpdates"></fetch-profil-updates>
</div>
</div>
</div>
<!-- END OF HIDDEN QUCK LINKS -->
<!-- MAIN PANNEL -->
<div class="col-sm-12 col-md-8 col-xxl-9 ">
@@ -303,30 +307,42 @@ export default {
<!-- ROW WITH THE PROFIL INFORMATION -->
<div class="row mb-4 ">
<div class="col-lg-12 col-xl-6 ">
<!-- PROFIL INFORMATION -->
<div class="row mb-4">
<div class="col">
<!-- PROFIL INFORMATION -->
<profil-information @showEditProfilModal="showEditProfilModal" :title="$p.t('profil','studentIn')" :data="profilInformation" :fotoStatus="fotoStatus"></profil-information>
</div>
</div>
<!-- QUICK LINKS, MOBILE VIEW (HIDDEN IF VIEWPORT >= MD BREAKPOINT) -->
<div v-if="quickLinks.length" class="row mb-4 d-md-none">
<div class="col">
<quick-links :title="$p.t('profil/quickLinks')" :links="quickLinks" />
</div>
</div>
<!-- CALENDAR SYNC OPTIONS, MOBILE VIEW (HIDDEN IF VIEWPORT >= MD BREAKPOINT) -->
<div class="row mb-4 d-md-none">
<div class="col">
<calendar-sync :uid="$props.data.username" :calendarSyncUrls="$props.calendarSyncUrls"></calendar-sync>
</div>
</div>
<!-- STUDENT INFO -->
<div class="row mb-4">
<div class=" col-lg-12">
<!-- STUDENT INFO -->
<role-information :title="$p.t('profil','studentInformation')" :data="roleInformation"></role-information>
</div>
</div>
<!-- START OF SECOND PROFIL INFORMATION COLUMN -->
</div>
<div class="col-xl-6 col-lg-12 ">
<!-- EMAILS -->
<div class="row mb-4">
<div class="col">
<!-- EMAILS -->
<profil-emails :title="this.$p.t('person','email')" :data="data.emails" ></profil-emails>
</div>
</div>
<!-- PRIVATE KONTAKTE-->
<div class="row mb-4 ">
<div class="col">
<!-- PRIVATE KONTAKTE-->
<div class="card">
<div class="card-header">
<div class="row">
@@ -349,9 +365,9 @@ export default {
</div>
</div>
<!-- PRIVATE ADRESSEN-->
<div class="row mb-4">
<div class="col">
<!-- PRIVATE ADRESSEN-->
<div class="card">
<div class="card-header">
<div class="row">
@@ -379,7 +395,7 @@ export default {
<div class="row">
<div class="col-12 mb-4" >
<core-filter-cmpt
v-if="preloadedPhrasen.loaded"
v-if="arePhrasesPreloaded"
@tableBuilt="betriebsmittelTableBuilt"
:title="$p.t('profil','entlehnteBetriebsmittel')"
ref="betriebsmittelTable"
@@ -389,11 +405,11 @@ export default {
</div>
<div class="col-12 mb-4" >
<core-filter-cmpt
v-if="preloadedPhrasen.loaded"
v-if="arePhrasesPreloaded"
@tableBuilt="zutrittsgruppenTableBuilt"
:title="$p.t('profil','zutrittsGruppen')"
ref="zutrittsgruppenTable"
:tabulator-options="zutrittsgruppen_table_options"
:tabulator-options="zutrittsgruppen_table_options"
tableOnly
:sideMenu="false"
noColumnFilter />
@@ -403,27 +419,34 @@ export default {
</div>
<!-- START OF SIDE PANEL -->
<div class="col-md-4 col-xxl-3 col-sm-12 text-break" >
<div v-if="quickLinks.length" class="row mb-4">
<div class="col">
<quick-links :title="$p.t('profil/quickLinks')" :links="quickLinks" />
</div>
</div>
<!-- Bearbeiten Button -->
<div class="row d-none d-md-block">
<div v-if="isEditable" class="row d-none d-md-block">
<div class="col mb-3">
<button @click="()=>showEditProfilModal()" type="button" class="card text-start w-100 btn btn-outline-secondary" >
<div class="row">
<div class="col-2">
<div class="col-auto">
<i class="fa fa-edit"></i>
</div>
<div class="col-10">{{$p.t('ui','bearbeiten')}}</div>
<div class="col-auto">{{$p.t('ui','bearbeiten')}}</div>
</div>
</button>
</div>
</div>
<!-- QUICK LINKS, HIDDEN IF VIEWPORT < MD BREAKPOINT -->
<div v-if="quickLinks.length" class="row mb-3 d-none d-md-block">
<div class="col">
<quick-links :title="$p.t('profil/quickLinks')" :links="quickLinks" />
</div>
</div>
<!-- CALENDAR SYNC OPTIONS, HIDDEN IF VIEWPORT < MD BREAKPOINT -->
<div class="row mb-3 d-none d-md-block">
<div class="col">
<calendar-sync :uid="$props.data.username" :calendarSyncUrls="$props.calendarSyncUrls"></calendar-sync>
</div>
</div>
<!-- PROFIL UPDATES -->
<div v-if="data.profilUpdates" class="row d-none d-md-block mb-3">
<div class="col mb-3">
<!-- PROFIL UPDATES -->
<fetch-profil-updates v-if="data.profilUpdates && data.profilUpdates.length" @fetchUpdates="fetchProfilUpdates" :data="data.profilUpdates"></fetch-profil-updates>
</div>
</div>
@@ -433,18 +456,13 @@ export default {
</div>
</div>
<!-- START OF THE SECOND ROW IN THE SIDE PANEL -->
<!-- MAILVERTEILER -->
<div class="row mb-3">
<div class="col">
<!-- HIER SIND DIE MAILVERTEILER -->
<mailverteiler :title="$p.t('profil','mailverteiler')" :data="data?.mailverteiler"></mailverteiler>
</div>
<!-- END OF THE SECOND ROW IN THE SIDE PANEL -->
</div>
<div class="row">
<div class="col">
<calendar-sync :uid="$props.data.username" :calendarSyncUrls="$props.calendarSyncUrls"></calendar-sync>
</div>
</div>
<!-- END OF THE SECOND ROW IN THE SIDE PANEL -->
<!-- END OF SIDE PANEL -->
</div>
<!-- END OF CONTAINER ROW-->
@@ -64,6 +64,10 @@ export default {
label: `${this.$p.t("person", "personenkennzeichen")}`,
value: this.data.personenkennzeichen,
},
matrikelnummer: {
label: this.$p.t('person/matrikelnummer'),
value: this.data.matrikelnummer
},
studiengang: {
label: `${this.$p.t("lehre", "studiengang")}`,
value: this.data.studiengang,
@@ -117,12 +121,12 @@ export default {
<profil-information :data="profilInformation" :title="$p.t('profil','studentIn')" :fotoStatus="fotoStatus"></profil-information>
</div>
</div>
<!-- SECOND ROW OF FIRST COLUMN -->
<div class="row mb-4">
<div class="col">
</div>
</div>
<!-- QUICK LINKS, MOBILE VIEW (HIDDEN IF VIEWPORT >= MD BREAKPOINT) -->
<div v-if="quickLinks.length" class="row mb-4 d-md-none">
<div class="col">
<quick-links :title="$p.t('profil/quickLinks')" :links="quickLinks" />
</div>
</div>
<!-- START OF SECOND PROFIL INFORMATION COLUMN -->
<!-- END OF PROFIL INFORMATION ROW -->
<!-- INFORMATION CONTENT END -->
@@ -151,7 +155,8 @@ export default {
<!-- START OF SIDE PANEL -->
<div class="col-md-4 col-xxl-3 col-sm-12 text-break" >
<!-- START OF THE FIRST ROW IN THE SIDE PANEL -->
<div v-if="quickLinks.length" class="row mb-4">
<!-- QUICK LINKS, HIDDEN IF VIEWPORT < MD BREAKPOINT -->
<div v-if="quickLinks.length" class="row mb-3 d-none d-md-block">
<div class="col">
<quick-links :title="$p.t('profil/quickLinks')" :links="quickLinks" />
</div>
@@ -0,0 +1,339 @@
import {CoreFilterCmpt} from "../../../components/filter/Filter.js";
import ApiPaabgabe from '../../../api/factory/paabgabeUebersicht.js'
import Loader from "../../Loader.js";
export const ProjektabgabeUebersicht = {
name: "ProjektabgabeUebersicht",
components: {
CoreFilterCmpt,
Loader
},
data() {
return {
phrasenPromise: null,
phrasenResolved: false,
tabulatorUuid: Vue.ref(0),
tableBuiltResolve: null,
tableBuiltPromise: null,
studiengaenge: null,
abgabetypen: null,
termine: null,
abgaben: null,
defaultStudiengang: {
studiengang_kz: null,
kuerzel: '-'
},
defaultTyp: {
paabgabetyp_kurzbz: null,
bezeichnung: '-'
},
defaultTermin: {
termin: null,
termin_anzeige: Vue.computed(() => this.$p.t('ui/alle'))
},
selectedStudiengang: null,
selectedAbgabetyp: null,
selectedTermin: null,
personSearchString: null,
paabgabeTableOptions: {
height: Vue.ref(400),
index: 'paabgabe_id',
layout: 'fitColumns',
//~ placeholder: this.$p.t('global/noDataAvailable'),
columns: [
{
title: Vue.computed(() => this.$p.t('global/aktionen')), field: 'actions',
formatter: (cell, formatterParams, onRendered) => {
let container = document.createElement('div');
container.className = "d-flex gap-2";
let downloadButton = document.createElement('button');
downloadButton.className = 'btn btn-outline-secondary';
downloadButton.innerHTML = '<i class="fa fa-download"></i>';
downloadButton.title = this.$p.t('ui', 'downloadDok');
downloadButton.addEventListener('click', evt => {
evt.stopPropagation();
this.actionDownload(cell.getData().paabgabe_id);
});
container.append(downloadButton);
if (this.showEdit)
{
let editButton = document.createElement('button');
editButton.className = 'btn btn-outline-secondary';
editButton.innerHTML = '<i class="fa fa-edit"></i>';
//editButton.addEventListener('click', () =>
//this.$refs.edit.open(cell.getData())
//);
container.append(editButton);
}
return container;
}
},
{title: Vue.computed(() => this.$p.t('abgabetool/paabgabeid')), field: 'paabgabe_id', visible: false},
{title: Vue.computed(() => this.$p.t('abgabetool/projektarbeitid')), field: 'projektarbeit_id', visible: false},
{
title: Vue.computed(() => this.$p.t('abgabetool/termin')),
field: "termin",
widthGrow: 1,
formatter: function (cell) {
const dateStr = cell.getValue();
if (!dateStr) return "";
const date = new Date(dateStr);
return date.toLocaleString("de-DE", {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour12: false
});
}
},
{title: Vue.computed(() => this.$p.t('abgabetool/c4abgabetyp')), field: 'paabgabetyp_bezeichnung'},
{title: Vue.computed(() => this.$p.t('person/uid')), field: 'uid'},
{title: Vue.computed(() => this.$p.t('person/vorname')), field: 'vorname'},
{title: Vue.computed(() => this.$p.t('person/nachname')), field: 'nachname'},
{title: Vue.computed(() => this.$p.t('abgabetool/c4projekttyp')), field: 'projekttyp_kurzbz'},
{title: Vue.computed(() => this.$p.t('abgabetool/c4titel')), field: 'titel'},
{title: Vue.computed(() => this.$p.t('abgabetool/personStatus')), field: 'personStatus'},
{
title: "in Visual Library",
field: 'in_visual_library',
formatter: (cell) => {
return cell.getValue() ? this.$p.t('ui/ja') : this.$p.t('ui/nein');
}
}
],
persistence: false,
},
paabgabeTableEventHandlers: [{
event: "tableBuilt",
handler: async () => {
this.tableBuiltResolve()
}
}
],
showEdit: null,
};
},
methods: {
tableResolve(resolve) {
this.tableBuiltResolve = resolve
},
setupData(){
//~ const d = data.map(paabgabe => {
//~ return {
//~ ort_kurzbz: paabgabe.ort_kurzbz,
//~ bezeichnung: paabgabe.bezeichnung.replace('&amp;', '&'),
//~ nummer: paabgabe.planbezeichnung,
//~ personen: paabgabe.max_person
//~ }
//~ })
this.$refs.paabgabeTable.tabulator.setData(this.abgaben);
},
// set placeholder text for no data table
setNoDataPlaceholder() {
this.$refs.paabgabeTable.tabulatorOptions.placeholder = this.$p.t('global/noDataAvailable');
},
loadStudiengaenge() {
this.$api.call(ApiPaabgabe.getStudiengaenge())
.then(res => {
this.studiengaenge = res?.data ?? []
})
},
loadPaabgabeTypes() {
this.$api.call(ApiPaabgabe.getPaAbgabetypen())
.then(res => {
this.abgabetypen = res?.data ?? []
})
},
loadTermine() {
this.$api.call(ApiPaabgabe.getTermine(this.selectedStudiengang, this.selectedAbgabetyp))
.then(res => {
this.selectedTermin = null;
this.termine = res?.data ?? []
})
},
loadPaAbgaben() {
this.$refs.loader.show();
this.$api.call(
ApiPaabgabe.getPaAbgaben(this.selectedStudiengang, this.selectedAbgabetyp, this.selectedTermin, this.personSearchString)
)
.then(res => {
this.$refs.loader.hide();
this.abgaben = res?.data ?? [];
this.setupData(res?.data ?? []);
});
},
handleUuidDefined(uuid) {
this.tabulatorUuid = uuid
},
//~ setRoute(val) {
//~ // TODO: router push
//~ },
async setupMounted() {
// load data for dropdowns
this.loadStudiengaenge();
this.loadPaabgabeTypes();
this.loadTermine();
// wait for table to build
this.tableBuiltPromise = new Promise(this.tableResolve);
await this.tableBuiltPromise;
// data placeholder after table built so phrases are available
this.setNoDataPlaceholder();
//this.loadPaAbgaben();
//~ const tableID = this.tabulatorUuid ? ('-' + this.tabulatorUuid) : ''
//~ const tableDataSet = document.getElementById('filterTableDataset' + tableID);
//~ if(!tableDataSet) return
//~ const rect = tableDataSet.getBoundingClientRect();
//~ const h = window.visualViewport.height - rect.top - 100
//~ if(this.$refs.raumsucheTable) {
//~ this.$refs.raumsucheTable.$refs.table.style.setProperty('height', h+'px')
//~ }
},
actionDownload(paabgabe_id) {
//~ window.open(
//~ FHC_JS_DATA_STORAGE_OBJECT.app_root
//~ + FHC_JS_DATA_STORAGE_OBJECT.ci_router
//~ +'/api/frontend/v1/education/paabgabeuebersicht/downloadProjektarbeit?paabgabe_id=' + encodeURIComponent(paabgabe_id),
//~ '_blank'
//~ );
},
// download zip file with all searched submission files
actionDownloadZip() {
const url = new URL(FHC_JS_DATA_STORAGE_OBJECT.app_root
+ FHC_JS_DATA_STORAGE_OBJECT.ci_router
+'/api/frontend/v1/education/PaabgabeUebersicht/downloadZip');
if (this.selectedStudiengang) url.searchParams.append('studiengang_kz', this.selectedStudiengang);
if (this.selectedAbgabetyp) url.searchParams.append('abgabetyp_kurzbz', this.selectedAbgabetyp);
if (this.selectedTermin) url.searchParams.append('abgabedatum', this.selectedTermin);
if (this.personSearchString) url.searchParams.append('personSearchString', this.personSearchString);
window.open(url.toString(), '_blank');
},
async getViewData() {
const viewDataResponse = await this.$api.call(ApiPaabgabe.getViewData());
const viewData = viewDataResponse.data;
this.showEdit = viewData.showEdit;
},
},
computed: {
isDarkMode() {
return this.$theme.theme_name.value == 'dark';
},
personSearchEnabled() {
return this.selectedStudiengang == null && this.selectedTermin == null && this.selectedAbgabetyp == null;
},
abgabeSearchEnabled() {
return this.personSearchString == '' || this.personSearchString == null;
},
zipDownloadUrl() {
return FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/api/frontend/v1/education/PaabgabeUebersicht/downloadZip';
}
},
async created() {
await this.getViewData();
this.phrasenPromise = this.$p.loadCategory(['abgabetool', 'global', 'person', 'ui']);
this.phrasenPromise.then(()=> {this.phrasenResolved = true});
},
mounted() {
this.setupMounted();
},
template: `
<h1 class="h3">{{$p.t('abgabetool/projektabgabeUebersicht')}}</h1>
<hr>
<div class="row">
<div class="col-12 col-lg-2">
<h6>{{ $p.t('abgabetool/studiengang') }}:</h6>
<select
ref="studiengang"
id="studiengangSelect"
v-model="selectedStudiengang"
class="form-select"
:aria-label="$p.t('abgabetool/studiengang_auswaehlen')"
:disabled="!abgabeSearchEnabled"
@change="loadTermine();"
>
<option :key="defaultStudiengang.studiengang_kz" selected :value="defaultStudiengang.studiengang_kz">{{defaultStudiengang.kuerzel}}</option>
<option v-for="stg in studiengaenge" :key="stg.studiengang_kz" :value="stg.studiengang_kz">{{stg.kuerzel}}</option>
</select>
</div>
<div class="col-12 col-lg-2">
<h6>{{ $p.t('abgabetool/c4abgabetyp') }}:</h6>
<select
ref="abgabetyp"
id="abgabetypSelect"
v-model="selectedAbgabetyp"
class="form-select"
:aria-label="$p.t('abgabetool/abgabetyp_auswaehlen')"
:disabled="!abgabeSearchEnabled"
@change="loadTermine();"
>
<option :key="defaultTyp.paabgabetyp_kurzbz" selected :value="defaultTyp.paabgabetyp_kurzbz">{{defaultTyp.bezeichnung}}</option>
<option v-for="typ in abgabetypen" :key="typ.paabgabetyp_kurzbz" :value="typ.paabgabetyp_kurzbz">{{typ.bezeichnung}}</option>
</select>
</div>
<div class="col-12 col-lg-2">
<h6>{{ $p.t('abgabetool/termin') }}:</h6>
<select
ref="termin"
id="terminSelect"
v-model="selectedTermin"
class="form-select"
:aria-label="$p.t('abgabetool/termin_auswaehlen')"
:disabled="!abgabeSearchEnabled"
>
<option :key="defaultTermin.termin" selected :value="defaultTermin.termin">{{defaultTermin.termin_anzeige}}</option>
<option v-for="termin in termine" :key="termin.termin" :value="termin.termin">{{termin.termin_anzeige}}</option>
</select>
</div>
<div class="col-12 col-lg-2">
<h6>{{ $p.t('abgabetool/personsuche') }}:</h6>
<input
type="text"
name="person-search"
class="form-control"
:placeholder="'name/uid/person ID/prestudent ID'"
:disabled="!personSearchEnabled"
v-on:keyup.enter="loadPaAbgaben"
v-model="personSearchString"
/>
</div>
<div class="col-12 col-lg-2 align-content-end">
<button class="btn btn-primary border-0" @click="loadPaAbgaben">{{ $p.t('abgabetool/anzeigen') }}</button>
</div>
<div class="col-12 col-lg-2 align-content-end">
<button class="btn btn-secondary border-0" @click="actionDownloadZip">{{ $p.t('abgabetool/zipDownload') }}</button>
</div>
</div>
<core-filter-cmpt
v-if="phrasenResolved"
@uuidDefined="handleUuidDefined"
ref="paabgabeTable"
:tabulator-options="paabgabeTableOptions"
:tabulator-events="paabgabeTableEventHandlers"
tableOnly
:sideMenu="false"
/>
<loader ref="loader" :timeout="0"></loader>
`
};
export default ProjektabgabeUebersicht;

Some files were not shown because too many files have changed in this diff Show More