Compare commits

..
Author SHA1 Message Date
Manuela Thamer 80969164ae add changes also to layouts classicFas, twoColumnsFormRight and TwoColumnsFormLeft 2026-08-20 14:59:16 +02:00
Manuela Thamer c5e6760f30 Refactoring of Notizlayout popupModal:
use PV Dropdowns Autocomplete for fields verfasser und bearbeiter
new function getDefaultVerfasser
2026-08-20 14:14:10 +02:00
41 changed files with 417 additions and 875 deletions
+1 -1
View File
@@ -84,7 +84,7 @@ $config['tabs'] =
'showCountNotes' => true,
//if true: section with following fields will be displayed: 'verfasser', 'bearbeiter', 'von', 'bis' and 'erledigt'
'showErweitert' => false,
'showErweitert' => true,
//notizlayout: choose of "classicFas", "twoColumnsFormLeft", twoColumnsFormRight, popupModal"
'notizLayout' => 'popupModal'
@@ -114,7 +114,6 @@ class LvPlan extends FHCAPI_Controller
// fetching lvplan events
$result = $this->stundenplanlib->getEventsUser($start_date, $end_date, $uid);
$lvplanEvents = $this->getDataOrTerminateWithError($result);
// fetching moodle events
@@ -167,9 +167,7 @@ class Studium extends FHCAPI_Controller
if($aktuelles_studienplan){
$lehrveranstaltungen = $this->computeStudienplanLehrveranstaltungen($aktuelles_studienplan->studienplan_id, $aktuelles_semester);
foreach($lehrveranstaltungen as $lehrv){
// a lehrveranstaltung without a modul has no children and gets its lektoren directly
$lvs = isset($lehrv->lehrveranstaltungen) ? $lehrv->lehrveranstaltungen : [$lehrv];
foreach($lvs as $lv){
foreach($lehrv->lehrveranstaltungen as $lv){
$lvLektoren =$this->computeLektorenFromLehrveranstaltung($lv->lehrveranstaltung_id,$aktuelles_semester, $aktuelles_studiengang->studiengang_kz, $aktuelles_studiensemester->studiensemester_kurzbz);
$lv->lektoren = $lvLektoren;
}
@@ -288,7 +286,6 @@ SELECT tbl_lehrveranstaltung.*,
$lehrveranstaltungen = $this->LehrveranstaltungModel->execReadOnlyQuery($query,[$studienplan_id, $semester]);
$lehrveranstaltungen = $this->getDataOrTerminateWithError($lehrveranstaltungen);
usort($lehrveranstaltungen, function($a, $b){
if($a->lehrtyp_kurzbz == "modul"){
return -1;
@@ -305,17 +302,12 @@ SELECT tbl_lehrveranstaltung.*,
}
else{
$parent =array_filter($carry, function($item)use($lehrv){
return isset($item->lehrveranstaltungen)
&& $item->studienplan_lehrveranstaltung_id == $lehrv->studienplan_lehrveranstaltung_id_parent;
return $item->studienplan_lehrveranstaltung_id == $lehrv->studienplan_lehrveranstaltung_id_parent;
});
$parent = current($parent);
if($parent){
$parent->lehrveranstaltungen[] = $lehrv;
}
else{
// lehrveranstaltung without a modul: keep it on the top level
array_push($carry, $lehrv);
}
}
return $carry;
}, []);
@@ -18,7 +18,7 @@ class NotizPerson extends Notiz_Controller
'loadDokumente' => ['admin:r', 'assistenz:r'],
'getMitarbeiter' => ['admin:r', 'assistenz:r'],
'isBerechtigt' => ['admin:r', 'assistenz:r'],
'getCountNotes' => ['admin:r', 'assistenz:r'],
'getCountNotes' => ['admin:r', 'assistenz:r']
]);
//Load Models
@@ -108,7 +108,6 @@ class Students extends FHCAPI_Controller
'studiensemester_kurzbz' => $studiensemester_kurzbz
]);
$studiensemester_kurzbz = $this->getStudiensemesterKurzbz($studiensemester_kurzbz);
$this->studentlistlib->addJoin(
"(
@@ -154,8 +153,7 @@ class Students extends FHCAPI_Controller
$this->addMeta('ci_params', [
'studiensemester_kurzbz' => $studiensemester_kurzbz
]);
$studiensemester_kurzbz = $this->getStudiensemesterKurzbz($studiensemester_kurzbz);
$this->studentlistlib->addJoin(
"(
@@ -215,7 +213,6 @@ class Students extends FHCAPI_Controller
'studiensemester_kurzbz' => $studiensemester_kurzbz
]);
$studiensemester_kurzbz = $this->getStudiensemesterKurzbz($studiensemester_kurzbz);
$this->studentlistlib->addJoin(
"(
+58 -10
View File
@@ -12,7 +12,7 @@ abstract class Notiz_Controller extends FHCAPI_Controller
public function __construct($permissions)
{
$default_permissions = [
'getUid' => self::DEFAULT_PERMISSION_R,
'getDefaultVerfasser' => self::DEFAULT_PERMISSION_R,
'getNotizen' => self::DEFAULT_PERMISSION_R,
'loadNotiz' => self::DEFAULT_PERMISSION_R,
'addNewNotiz' => self::DEFAULT_PERMISSION_RW,
@@ -21,7 +21,7 @@ abstract class Notiz_Controller extends FHCAPI_Controller
'loadDokumente' => self::DEFAULT_PERMISSION_R,
'getMitarbeiter' => self::DEFAULT_PERMISSION_R,
'isBerechtigt' => self::DEFAULT_PERMISSION_R,
'getCountNotes' => self::DEFAULT_PERMISSION_R,
'getCountNotes' => self::DEFAULT_PERMISSION_R
];
if(!is_array($permissions))
@@ -46,9 +46,20 @@ abstract class Notiz_Controller extends FHCAPI_Controller
]);
}
public function getUid()
public function getDefaultVerfasser()
{
$this->terminateWithSuccess(getAuthUID());
$uid = getAuthUID();
$this->load->model('ressource/Mitarbeiter_model', 'MitarbeiterModel');
$this->MitarbeiterModel->addSelect('p.person_id, p.nachname, p.vorname, p.titelpost, p.titelpre, ben.uid');
$this->MitarbeiterModel->addJoin('public.tbl_benutzer ben', 'ON (ben.uid = public.tbl_mitarbeiter.mitarbeiter_uid)', 'LEFT');
$this->MitarbeiterModel->addJoin('public.tbl_person p', 'ON (p.person_id = ben.person_id)', 'LEFT');
$result = $this->MitarbeiterModel->loadWhere(
array('mitarbeiter_uid' => $uid)
);
if(hasData($result))
$data = getData($result);
$this->terminateWithSuccess(current($data));
}
@@ -106,7 +117,7 @@ abstract class Notiz_Controller extends FHCAPI_Controller
$this->terminateWithError("in abstract function: define right in extension", self::ERROR_TYPE_GENERAL);
}
public function loadNotiz()
/* public function loadNotiz()
{
$_POST = json_decode(utf8_encode($this->input->raw_input_stream), true);
@@ -118,6 +129,43 @@ abstract class Notiz_Controller extends FHCAPI_Controller
THEN public.tbl_notiz.updateamum ELSE public.tbl_notiz.insertamum END::timestamp, 'DD.MM.YYYY HH24:MI:SS') AS lastUpdate");
$this->NotizModel->addLimit(1);
$result = $this->NotizModel->loadWhere(
array('notiz_id' => $notiz_id)
);
if (isError($result))
{
$this->terminateWithError($result, self::ERROR_TYPE_GENERAL);
}
elseif (!hasData($result))
{
$this->terminateWithError($this->p->t('ui', 'error_missingId', ['id'=>'Notiz_id']), self::ERROR_TYPE_GENERAL);
}
else
{
$this->terminateWithSuccess(current(getData($result)));
}
}*/
public function loadNotiz()
{
$_POST = json_decode(utf8_encode($this->input->raw_input_stream), true);
$notiz_id = $this->input->post('notiz_id');
$this->NotizModel->addSelect('public.tbl_notiz.*');
$this->NotizModel->addSelect("TO_CHAR(CASE WHEN public.tbl_notiz.updateamum >= public.tbl_notiz.insertamum
THEN public.tbl_notiz.updateamum ELSE public.tbl_notiz.insertamum END::timestamp, 'DD.MM.YYYY HH24:MI:SS') AS lastUpdate");
$this->NotizModel->addSelect('perb.person_id AS perb_person_id, perb.vorname AS perb_vorname, perb.nachname AS perb_nachname,
perb.titelpre AS perb_titelpre, perb.titelpost AS perb_titelpost, benb.uid AS perb_uid');
$this->NotizModel->addSelect('perv.person_id AS perv_person_id, perv.vorname AS perv_vorname, perv.nachname AS perv_nachname,
perv.titelpre AS perv_titelpre, perv.titelpost AS perv_titelpost, benv.uid AS perv_uid');
$this->NotizModel->addJoin('public.tbl_notiz_dokument', 'notiz_id', 'LEFT');
$this->NotizModel->addJoin('public.tbl_benutzer benb', 'ON (benb.uid = public.tbl_notiz.bearbeiter_uid)', 'LEFT');
$this->NotizModel->addJoin('public.tbl_person perb', 'ON (perb.person_id = benb.person_id)', 'LEFT');
$this->NotizModel->addJoin('public.tbl_benutzer benv', 'ON (benv.uid = public.tbl_notiz.verfasser_uid)', 'LEFT');
$this->NotizModel->addJoin('public.tbl_person perv', 'ON (perv.person_id = benv.person_id)', 'LEFT');
$this->NotizModel->addLimit(1);
$result = $this->NotizModel->loadWhere(
array('notiz_id' => $notiz_id)
);
@@ -238,7 +286,6 @@ abstract class Notiz_Controller extends FHCAPI_Controller
public function updateNotiz()
{
$this->load->library('form_validation');
$this->load->library('DmsLib');
@@ -269,7 +316,7 @@ abstract class Notiz_Controller extends FHCAPI_Controller
$uid = getAuthUID();
$titel = $post_data['titel'];
$text = $post_data['text'];
$bearbeiter_uid = isset($post_data['bearbeiter']) ? $post_data['bearbeiter'] : $post_data['bearbeiter_uid'];
$bearbeiter_uid = $post_data['bearbeiter'];
$erledigt = $post_data['erledigt'];
$start = $post_data['start'];
$ende = $post_data['ende'];
@@ -436,14 +483,15 @@ abstract class Notiz_Controller extends FHCAPI_Controller
$this->terminateWithSuccess(getData($result));
}
public function getMitarbeiter($searchString)
public function getMitarbeiter()
{
$searchString = $this->input->get('searchString') ?? '';
$this->load->model('ressource/Mitarbeiter_model', 'MitarbeiterModel');
$result = $this->MitarbeiterModel->searchMitarbeiter($searchString);
$result = $this->MitarbeiterModel->searchMitarbeiter($searchString, 'mitAkadGrad');
if (isError($result)) {
$this->terminateWithError($result, self::ERROR_TYPE_GENERAL);
}
$this->terminateWithSuccess($result);
$this->terminateWithSuccess($result ?: []);
}
public function getCountNotes($person_id)
-15
View File
@@ -82,21 +82,6 @@ function generateCSSsInclude($CSSs)
}
}
/**
* Generates tags for the style sheets you want to include, the parameter could by a string or an array of strings
*/
function generatePrintCSS($path)
{
$cssLink = '<link rel="stylesheet" media="print" href="%s" />';
$ci =& get_instance();
$cachetoken = '?'.$ci->config->item('fhcomplete_build_version');
if (isset($path)) {
echo sprintf($cssLink, base_url($path).$cachetoken) . PHP_EOL;
}
}
/**
* Generates global JS-Object to pass parms to other javascripts
*/
+2 -3
View File
@@ -94,13 +94,13 @@ class StundenplanLib
if (isError($benutzer_gruppen))
return $benutzer_gruppen;
$benutzer_gruppen = getData($benutzer_gruppen);
$this->_ci->addMeta('spezialgruppen', $benutzer_gruppen);
// getting the student_lehrverbaende of the student in the different studiensemester
$student_lehrverband = $this->fetchStudentlehrverbandFromStudiensemester($student_uid, $semester_range);
if (isError($student_lehrverband))
return $student_lehrverband;
$student_lehrverband = getData($student_lehrverband);
$this->_ci->addMeta('studentlehrvebaende', $student_lehrverband);
$stundenplan_query = $this->_ci->StundenplanModel->getStundenplanQuery(
$start,
$end,
@@ -632,7 +632,6 @@ class StundenplanLib
$result->semester = $item->semester;
$result->verband = $item->verband;
$result->gruppe = $item->gruppe;
$result->studiensemester_kurzbz = $item->studiensemester_kurzbz;
return $result;
},
$lehrverband_query_result);
@@ -28,21 +28,10 @@ class CoreMissingZgvTagLib
$this->ci->PrestudentModel->addJoin('public.tbl_prestudentstatus', 'prestudent_id');
$this->ci->PrestudentModel->addJoin('public.tbl_benutzer bn', 'person_id');
$this->ci->PrestudentModel->addJoin('public.tbl_studiengang', 'studiengang_kz');
$this->ci->PrestudentModel->db->group_start();
$this->ci->PrestudentModel->db->where('zgvdatum', null);
$this->ci->PrestudentModel->db->where('typ', 'b');
$this->ci->PrestudentModel->db->or_group_start();
$this->ci->PrestudentModel->db->where('zgvmadatum', null);
$this->ci->PrestudentModel->db->where('typ', 'm');
$this->ci->PrestudentModel->db->group_end();
$this->ci->PrestudentModel->db->group_end();
$result = $this->ci->PrestudentModel-> loadWhere(array(
'bn.aktiv' => true,
'bn.aktiv' => true, //check if necessary
'zgvdatum' => null,
'typ' => 'b',
'studiensemester_kurzbz' => $semester
));
@@ -71,36 +60,16 @@ class CoreMissingZgvTagLib
$semester = $params['studiensemester_kurzbz'];
$prestudent_id = $params['id'];
$this->ci->PrestudentModel->addSelect('prestudent_id');
$this->ci->PrestudentModel->addSelect('start as von');
$this->ci->PrestudentModel->addSelect('ende as bis');
$this->ci->PrestudentModel->addJoin('public.tbl_prestudentstatus', 'prestudent_id');
//add studiensemester also here for manual rebuild
$this->ci->PrestudentModel->addJoin('public.tbl_studiensemester', 'studiensemester_kurzbz');
$this->ci->PrestudentModel->addJoin('public.tbl_benutzer bn', 'person_id');
$this->ci->PrestudentModel->addJoin('public.tbl_studiengang', 'studiengang_kz');
$this->ci->PrestudentModel->db->group_start();
$this->ci->PrestudentModel->db->where('zgvdatum', null);
$this->ci->PrestudentModel->db->where('typ', 'b');
$this->ci->PrestudentModel->db->or_group_start();
$this->ci->PrestudentModel->db->where('zgvmadatum', null);
$this->ci->PrestudentModel->db->where('typ', 'm');
$this->ci->PrestudentModel->db->group_end();
$this->ci->PrestudentModel->db->group_end();
$this->ci->PrestudentModel->db->limit(1);
$result = $this->ci->PrestudentModel-> loadWhere(array(
'bn.aktiv' => true,
$result = $this->ci->PrestudentModel->loadWhere(array(
'bn.aktiv' => true, //check if necessary
'zgvdatum' => null,
'typ' => 'b',
'studiensemester_kurzbz' => $semester,
'prestudent_id' => $prestudent_id
));
if(hasData($result))
{
return $result;
@@ -225,10 +225,6 @@ class Gehaltsbestandteil extends AbstractBestandteil implements \JsonSerializabl
$grundbetrag = number_format($grundbetrag, 2, '.', '');
}
$this->markDirty('grundbetrag', $this->grundbetrag, $grundbetrag);
if(in_array('grundbetrag', $this->modifiedcolumns))
{
$this->setBetrag_valorisiert($grundbetrag);
}
$this->grundbetrag = $grundbetrag;
return $this;
}
+1 -1
View File
@@ -204,7 +204,7 @@ class Student_model extends DB_Model
Events::trigger(
'generate_student_uid',
function ($value) use (&$uid) {
function ($value) use ($uid) {
$uid = $value;
},
$stgkzl,
@@ -457,7 +457,6 @@ class Studiengang_model extends DB_Model
*/
public function getLeitung($studiengang_kz = null)
{
$this->addDistinct();
$this->addSelect('uid, studiengang_kz, oe_kurzbz, vorname, nachname, email, titelpre, titelpost, alias');
$this->addJoin('public.tbl_benutzerfunktion', 'oe_kurzbz');
$this->addJoin('public.tbl_benutzer', 'uid');
@@ -604,17 +604,11 @@ class Stundenplan_model extends DB_Model
{
foreach($semester_date_range as $sem_date => $sem_date_range)
{
if(!array_key_exists($sem,$studentlehrverbaende) || count($studentlehrverbaende[$sem]) == 0)
if(!array_key_exists($sem_date,$studentlehrverbaende) || count($studentlehrverbaende[$sem_date]) == 0)
{
continue;
}
$studlvbds = array_filter(
$studentlehrverbaende[$sem],
function($value) use ($sem_date) {
return $value->studiensemester_kurzbz === $sem_date;
}
);
foreach($studlvbds as $key=>$lehrverband)
foreach($studentlehrverbaende[$sem_date] as $key=>$lehrverband)
{
$query .= "(((sp.studiengang_kz = ".$this->escape($lehrverband->studiengang_kz)." AND sp.semester = ".$this->escape($lehrverband->semester)." AND sp.verband = ".$this->escape($lehrverband->verband)." AND sp.gruppe = ".$this->escape($lehrverband->gruppe)." AND sp.datum BETWEEN ".$this->escape($sem_date_range->start)." AND ".$this->escape($sem_date_range->ende).")";
// Eintraege fuer den ganzen Verband
@@ -16,7 +16,7 @@ $query = '
anrechnung.begruendung_id,
anrechnung.dms_id,
CASE
WHEN stg.oe_kurzbz IN (' . $ORGANISATIONSEINHEITEN_SCHREIBBERECHTIGT . ') THEN TRUE
WHEN stg.typ || stg.kurzbz IN (' . $ORGANISATIONSEINHEITEN_SCHREIBBERECHTIGT . ') THEN TRUE
ELSE FALSE
END "schreibberechtigt",
anrechnung.studiensemester_kurzbz,
@@ -7,7 +7,6 @@ $includesArray = array(
'fontawesome6' => true,
'axios027' => true,
'primevue3' => true,
'printCssPath' => 'public/css/Cis4/print.css',
'customJSModules' => array_merge([
'public/js/apps/Cis/Menu.js'
], $customJSModules ?? []),
@@ -12,8 +12,6 @@
$customJSs = isset($customJSs) ? $customJSs : null;
$customJSModules = isset($customJSModules) ? $customJSModules : null;
$skipID = isset($skipID) ? $skipID : null;
$printCssPath = isset($printCssPath) ? $printCssPath : null;
?>
<!-- Header start -->
@@ -140,9 +138,6 @@
generateCSSsIncludeIfExtensionCssExists('tags.css');
}
// Print CSS
if ($printCssPath) generatePrintCSS($printCssPath);
$extapphelper = ExtendableAppsHelper::getInstance();
$extapphelper->init($customCSSs, $customJSs, $customJSModules);
-9
View File
@@ -59,21 +59,12 @@
padding: .25em .5em .25em .25em;
margin-inline-end: .25rem;
}
.rangePageContainer .calendar-event-default .event-time {
display: none !important;
}
.weekPageContainer .calendar-event-default .event-text {
display: flex;
flex-direction: column;
justify-content: space-evenly;
align-items: center;
}
.rangePageContainer .calendar-event-default .event-text {
display: flex;
flex-direction: column;
justify-content: space-evenly;
align-items: start;
}
.dayPageContainer .calendar-event-default,
.listPageContainer .calendar-event-default {
display: flex;
-51
View File
@@ -1,51 +0,0 @@
@page {
size: landscape;
}
* {
background-color: white !important;
color: black !important;
font-size: x-small !important;
overflow: visible !important;
}
.fhc-calendar-base-slider > div {
position: relative !important;
}
header,
#skiplink,
#cis-lvplan-personal-heading,
.cis-lvplan-personal hr,
.fhc-calendar-base-header,
.fhc-calendar-mode-range .grid-allday,
.fhc-calendar-mode-range #rangePresetSelector,
.fhc-calendar-base-grid-line-background {
display: none !important;
}
.fhc-calendar-mode-range .main-header-sunday,
.fhc-calendar-mode-range .fhc-calendar-base-grid-line-sunday {
border-bottom-width: 0 !important;
}
.fhc-calendar-base-grid-line-sunday,
.fhc-calendar-mode-range .main-header-sunday {
break-after: page !important;
}
.fhc-calendar-mode-range .fhc-calendar-base-grid {
grid-template-rows: auto repeat(var(--fhc-grid-displayed-axis-main-count), auto) !important;
gap: 0px !important;
}
.fhc-calendar-mode-range .fhc-calendar-base-grid * {
gap: 0px !important;
}
.fhc-calendar-mode-range .main-header:not(.main-header-empty),
.fhc-calendar-mode-range .fhc-calendar-base-grid-line {
border-top: solid !important;
border-top-color: black !important;
border-top-width: 1px !important;
}
-13
View File
@@ -62,16 +62,3 @@
top:13%;"
z-index: 104; font-size: 1rem; width: 2.5rem; height: 2.5rem; opacity:0; transition: opacity 0.2s; top:13%;"
}*/
.tag-hover:hover {
background-color: #e9ecef;
}
.green {
background-color: #e9ecef;
}
.btn-hover {
--bs-btn-hover-bg: #e9ecef; /* Neue Hintergrundfarbe beim Hover */
--bs-btn-hover-border-color: #e9ecef; /* Neue Rahmenfarbe beim Hover */
}
+1 -7
View File
@@ -104,17 +104,11 @@
.fhc-calendar-base-grid-line {
grid-gap: var(--fhc-calendar-gap-events, var(--fhc-calendar-gap, 1px));
}
.fhc-calendar-mode-range .main-header-sunday,
.fhc-calendar-mode-range .all-day-events-sunday,
.fhc-calendar-mode-range .fhc-calendar-base-grid-line-sunday {
border: solid;
border-width: 0 0 8px 0;
border-color: var(--fhc-calendar-gap);
}
.fhc-calendar-base-label-time {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: end;
}
/* Header */
.fhc-calendar-base-header .header-picker {
-5
View File
@@ -145,8 +145,3 @@
float: right;
margin-top: 3px;
}
.btn-hover {
--bs-btn-hover-bg: #e9ecef;
--bs-btn-hover-border-color: #e9ecef;
}
+9 -2
View File
@@ -28,6 +28,12 @@ export default {
url: 'api/frontend/v1/notiz/notizPerson/getUid/'
};
},
getDefaultVerfasser() {
return {
method: 'get',
url: 'api/frontend/v1/notiz/notizPerson/getDefaultVerfasser/'
};
},
addNewNotiz(id, params) {
return {
method: 'post',
@@ -71,10 +77,11 @@ export default {
params
};
},
getMitarbeiter(event) {
getMitarbeiter(searchString) {
return {
method: 'get',
url: 'api/frontend/v1/notiz/notizPerson/getMitarbeiter/' + event
url: 'api/frontend/v1/notiz/notizPerson/getMitarbeiter',
params: { searchString }
};
},
isBerechtigt(id, type_id) {
+1 -1
View File
@@ -190,7 +190,7 @@ const router = VueRouter.createRouter({
}
},
{
path: `/Cis/MyLvPlan/:mode?/:focus_date?/:range_length?`,
path: `/Cis/MyLvPlan/:mode?/:focus_date?`,
name: 'MyLvPlan',
component: MyLvPlan,
props(route) {
+2 -16
View File
@@ -93,10 +93,6 @@ export default {
type: Boolean,
default: undefined
},
btnRange: {
type: Boolean,
default: undefined
},
timeGrid: Array,
draggableEvents: [Boolean, Array, Function],
dropableEvents: [Boolean, Array, Function],
@@ -167,16 +163,8 @@ export default {
return date.setLocale(this.locale);
},
set(value) {
let date;
let rangeLength;
if (value instanceof luxon.DateTime) {
date = value;
} else {
date = value.date;
rangeLength = value.rangeLength;
}
this.internalDate = date;
this.$emit('update:date', date, this.cMode, rangeLength);
this.internalDate = value;
this.$emit('update:date', value, this.cMode);
}
},
sMode() {
@@ -294,7 +282,6 @@ export default {
:btn-week="!!modes['week'] && (btnWeek || (showBtns && btnWeek !== false))"
:btn-month="!!modes['month'] && (btnMonth || (showBtns && btnMonth !== false))"
:btn-list="!!modes['list'] && (btnList || (showBtns && btnList !== false))"
:btn-range="!!modes['range'] && (btnRange || (showBtns && btnRange !== false))"
:mode-options="modeOptions ? modeOptions[cMode] : undefined"
>
<slot name="actions" />
@@ -306,7 +293,6 @@ export default {
@update:range="$emit('update:range', $event)"
@request-modal-open="showEventModal"
@request-modal-close="hideEventModal"
@update:date="cDate = $event"
v-bind="modeOptions ? modeOptions[cMode] : null || {}"
>
<template v-slot="slot"><slot v-bind="slot" /></template>
+70 -121
View File
@@ -53,11 +53,7 @@ export default {
flipAxis: Boolean,
allDayEvents: Boolean,
axisMainCollapsible: Boolean,
snapToGrid: Boolean,
arePartHeadersRepeated: {
type: Boolean,
default: false,
},
snapToGrid: Boolean
},
data() {
return {
@@ -114,19 +110,6 @@ export default {
return this.axisParts.slice(0, -1);
return this.axisParts;
},
displayedAxisMain() {
if (!this.$props.arePartHeadersRepeated)
return this.$props.axisMain;
let displayedAxisMain = [];
this.$props.axisMain.forEach((part, index) => {
if (part.weekday === 1 && index > 0) {
displayedAxisMain.push(null);
}
displayedAxisMain.push(part);
});
return displayedAxisMain;
},
start() {
return this.axisPartsWithBreaks[0].start;
},
@@ -145,17 +128,15 @@ export default {
return ends;
},
axisMainBorders() {
return this.displayedAxisMain.reduce(
(res, curr) => res.concat(curr ? [curr.plus(this.start), curr.plus(this.end)] : [null, null]),
return this.axisMain.reduce(
(res, curr) => res.concat([curr.plus(this.start), curr.plus(this.end)]),
[]
);
},
eventsAllDay() {
if (!this.allDayEvents)
return [];
let eventsAllDay = this.mapIntoMainAxis(this.originalEvents.filter(event => event.orig.allDayEvent));
return eventsAllDay;
return this.mapIntoMainAxis(this.originalEvents.filter(event => event.orig.allDayEvent));
},
eventsNormal() {
if (!this.allDayEvents)
@@ -172,7 +153,7 @@ export default {
return this.events.find(e => e.length);
},
styleGridCols() {
let cols = 'repeat(' + this.displayedAxisMain.length + ', 1fr)';
let cols = 'repeat(' + this.axisMain.length + ', 1fr)';
if (this.axisMainCollapsible) {
if (this.hasValidEvents)
cols = this.events
@@ -200,7 +181,7 @@ export default {
});
this.eventsNormal.forEach((events, mainIndex) => {
let day = this.displayedAxisMain[mainIndex];
let day = this.axisMain[mainIndex];
events.forEach(event => {
if (!event.startsHere && !event.endsHere)
return;
@@ -230,17 +211,15 @@ export default {
},
methods: {
mapIntoMainAxis(target) {
const result = Array.from({length: this.displayedAxisMain.length}, () => Array());
const result = Array.from({length: this.axisMain.length}, () => Array());
target.forEach(event => {
const start = event.start || this.axisMainBorders[0].plus(-1);
const end = event.end || this.axisMainBorders[this.axisMainBorders.length - 1].plus(1);
this.displayedAxisMain.forEach((part, index) => {
if (!part) return;
let laneStart = this.axisMainBorders[index * 2];
let laneEnd = this.axisMainBorders[index * 2 + 1];
for (var i = 0; i < this.axisMain.length; i++) {
let laneStart = this.axisMainBorders[i * 2];
let laneEnd = this.axisMainBorders[i * 2 + 1];
if (event.orig?.allDayEvent) {
laneStart = laneStart.startOf('day');
laneEnd = laneEnd.endOf('day');
@@ -248,13 +227,13 @@ export default {
if (start < laneEnd && end > laneStart) {
const startsHere = start >= laneStart;
const endsHere = end <= laneEnd;
result[index].push({
result[i].push({
...event,
startsHere,
endsHere
});
}
});
}
});
return result;
@@ -338,31 +317,22 @@ export default {
<div
class="fhc-calendar-base-grid"
style="display:grid;width:100%;height:100%;overflow:auto"
:style="'--fhc-grid-displayed-axis-main-count: ' + displayedAxisMain.length + ';grid-template-' + axisRow + 's:auto' + (allDayEvents ? ' auto ' : ' ') + '1fr;grid-template-' + axisCol + 's:auto ' + styleGridCols"
:style="'grid-template-' + axisRow + 's:auto' + (allDayEvents ? ' auto ' : ' ') + '1fr;grid-template-' + axisCol + 's:auto ' + styleGridCols"
>
<div
class="grid-header"
style="display:grid"
:style="'grid-template-' + axisCol + 's:subgrid;grid-' + axisCol + ':1/-1'"
>
<template v-for="(date, index) in displayedAxisMain" :key="index">
<div
v-if="date"
class="main-header"
:class="{
'collapsed-header': axisMainCollapsible && hasValidEvents && !events[index].length,
'main-header-sunday': date.weekday === 7,
}"
:style="'grid-' + axisCol + ':' + (2+index)"
>
<slot name="main-header" v-bind="{ index, date }" />
</div>
<div
v-else
class="main-header main-header-empty"
:style="'grid-' + axisCol + ':' + (2+index)"
></div>
</template>
<div
v-for="(date, index) in axisMain"
:key="index"
class="main-header"
:class="{'collapsed-header': axisMainCollapsible && hasValidEvents && !events[index].length}"
:style="'grid-' + axisCol + ':' + (2+index)"
>
<slot name="main-header" v-bind="{ index, date }" />
</div>
</div>
<div
v-if="allDayEvents"
@@ -370,34 +340,26 @@ export default {
style="display:grid"
:style="'grid-template-' + axisCol + 's:subgrid;grid-' + axisCol + ':1/-1'"
>
<template v-for="(events, index) in eventsAllDay" :key="index">
<div
v-if="displayedAxisMain[index]"
class="all-day-events"
:class="{'all-day-events-sunday': displayedAxisMain[index].weekday === 7}"
:style="'grid-' + axisCol + ':' + (2+index)"
<div
v-for="(events, index) in eventsAllDay"
:key="index"
class="all-day-events"
:style="'grid-' + axisCol + ':' + (2+index)"
>
<grid-line-event
v-for="(event, i) in events"
:key="i"
:event="event"
>
<grid-line-event
v-for="(event, i) in events"
:key="i"
:event="event"
>
<template v-slot="slot">
<slot name="event" v-bind="slot" />
</template>
</grid-line-event>
</div>
<div
v-else
class="all-day-events"
:style="'grid-' + axisCol + ':' + (2+index)"
></div>
</template>
<template v-slot="slot">
<slot name="event" v-bind="slot" />
</template>
</grid-line-event>
</div>
</div>
<div
ref="scroller"
@scrollend="userScroll ? disableAutoScroll() : userScroll = true"
id="grid-main-scrollable"
style="display:grid;overflow:auto"
:style="'grid-' + axisCol + ':1/-1;grid-template-' + axisCol + 's:subgrid'"
>
@@ -409,9 +371,9 @@ export default {
>
<div
v-for="(part, index) in axisPartsSave"
:key="index"2
:key="index"
class="part-header"
:style="'grid-' + axisCol + ':1;grid-' + axisRow + ': ps_' + index + '/pe_' + index + ';min-width:50px;'"
:style="'grid-' + axisCol + ':1;grid-' + axisRow + ': ps_' + index + '/pe_' + index"
>
<slot name="part-header" v-bind="{ index, part }" />
</div>
@@ -427,56 +389,43 @@ export default {
@dragover="dropAllowed ? $event.preventDefault() : null"
>
<template
v-for="(date, index) in displayedAxisMain"
v-for="(date, index) in axisMain"
:key="index"
>
<template v-if="date">
<div
v-for="(part, i) in axisPartsSave"
:key="i"
class="part-body"
style="position:relative"
:style="'grid-' + axisCol + ':' + (1+index) + ';grid-' + axisRow + ':ps_' + i + '/pe_' + i"
>
<slot name="part-body" v-bind="{ index, part }" />
<div
v-for="(part, i) in axisPartsSave"
:key="i"
class="part-body"
style="position:relative"
:style="'grid-' + axisCol + ':' + (1+index) + ';grid-' + axisRow + ':ps_' + i + '/pe_' + i"
>
<slot name="part-body" v-bind="{ index, part }" />
v-if="snapToGrid && dragging"
style="position:absolute;inset:0;z-index:1"
v-cal-dnd:dropzone.once="{date: date.plus(part.start || part), ends: ends.slice(ends.findIndex(end => end > date))}"
></div>
</div>
<grid-line
:start="date.plus(start)"
:end="date.plus(end)"
:date="date"
:events="eventsNormal[index]"
:backgrounds="backgrounds[index]"
style="position:relative"
:style="'grid-' + axisRow + ':1/-1;grid-' + axisCol + ':' + (1+index)"
>
<template #event="slot">
<slot name="event" v-bind="slot" />
</template>
<template #dropzone>
<div
v-if="snapToGrid && dragging"
v-if="!snapToGrid && dragging"
style="position:absolute;inset:0;z-index:1"
v-cal-dnd:dropzone.once="{date: date.plus(part.start || part), ends: ends.slice(ends.findIndex(end => end > date))}"
v-cal-dnd:dropzone="evt => getTimestampFromMouse(evt, date)"
></div>
</div>
<grid-line
:start="date.plus(start)"
:end="date.plus(end)"
:date="date"
:events="eventsNormal[index]"
:backgrounds="backgrounds[index]"
style="position:relative"
:style="'grid-' + axisRow + ':1/-1;grid-' + axisCol + ':' + (1+index)"
>
<template #event="slot">
<slot name="event" v-bind="slot" />
</template>
<template #dropzone>
<div
v-if="!snapToGrid && dragging"
style="position:absolute;inset:0;z-index:1"
v-cal-dnd:dropzone="evt => getTimestampFromMouse(evt, date)"
></div>
</template>
</grid-line>
</template>
<template v-else>
<div
v-for="(part, i) in axisPartsSave"
:key="i"
class="part-header-repeat part-body"
style="position:relative"
:style="'grid-' + axisCol + ':' + (1+index) + ';grid-' + axisRow + ':ps_' + i + '/pe_' + i"
>
<slot name="part-header-repeat" v-bind="{ index, part }" />
</div>
</template>
</template>
</grid-line>
</template>
</div>
</div>
@@ -123,9 +123,6 @@ export default {
template: /* html */ `
<div
class="fhc-calendar-base-grid-line"
:class="{
'fhc-calendar-base-grid-line-sunday': $props.date.weekday === 7
}"
style="position:relative;display:grid;grid-auto-flow:dense"
:style="'grid-template-' + axisRow + 's:subgrid'"
>
+4 -17
View File
@@ -24,8 +24,7 @@ export default {
btnMonth: Boolean,
btnWeek: Boolean,
btnDay: Boolean,
btnList: Boolean,
btnRange: Boolean,
btnList: Boolean
},
emits: [
"next",
@@ -36,8 +35,7 @@ export default {
],
data() {
return {
open: false,
semesterOptions: [],
open: false
};
},
methods: {
@@ -55,15 +53,6 @@ export default {
</div>
<div class="header-modes">
<div class="d-flex gap-1 justify-content-end" role="group">
<button
v-if="btnRange"
type="button"
class="btn btn-outline-secondary"
:class="{active: mode === 'range'}"
@click="clickMode($event, 'range')"
>
<i class="fa fa-calendar"></i>
</button>
<button
v-if="btnMonth"
type="button"
@@ -106,10 +95,9 @@ export default {
<div class="header-picker">
<div class="btn-group" role="group">
<button
v-if="$props.mode !== 'range'"
class="btn btn-outline-secondary border-0"
@click="$emit('prev')"
:disabled="open"
class="btn btn-outline-secondary border-0"
>
<i class="fa fa-chevron-left"></i>
</button>
@@ -122,10 +110,9 @@ export default {
:list-length="modeOptions.length"
/>
<button
v-if="$props.mode !== 'range'"
class="btn btn-outline-secondary border-0"
@click="$emit('next')"
:disabled="open"
class="btn btn-outline-secondary border-0"
>
<i class="fa fa-chevron-right"></i>
</button>
@@ -5,11 +5,10 @@ export default {
components: {
VueDatePicker
},
inject: {
locale: "locale",
timezone: "timezone",
rangeLength: {default: 30}
},
inject: [
"locale",
"timezone"
],
props: {
date: {
type: luxon.DateTime,
@@ -22,7 +21,7 @@ export default {
listLength: {
type: Number,
default: 7
},
}
},
emits: [
"update:date"
@@ -39,8 +38,6 @@ export default {
return {month: this.convertedDate.month-1, year: this.convertedDate.year};
case "list":
return [this.convertedDate.startOf('day').ts, this.convertedDate.startOf('day').plus({ days: this.listLength }).ts - 1];
case "range":
return [this.convertedDate.startOf('day').ts, this.convertedDate.startOf('day').plus({ days: this.rangeLength }).ts - 1];
case "week":
return [this.convertedDate.startOf('week', { useLocaleWeeks: true }).ts, this.convertedDate.endOf('week', { useLocaleWeeks: true }).ts];
case "day":
@@ -59,8 +56,6 @@ export default {
return this.$p.t('calendar/year_kw', { year, week });
case "list":
return this.date.toLocaleString(luxon.DateTime.DATE_FULL) + '-' + this.date.plus({ days: this.listLength - 1 }).toLocaleString(luxon.DateTime.DATE_FULL);
case "range":
return this.date.toLocaleString(luxon.DateTime.DATE_FULL) + '-' + this.date.plus({ days: this.rangeLength - 1 }).toLocaleString(luxon.DateTime.DATE_FULL);
case "day":
return this.date.toLocaleString(luxon.DateTime.DATE_FULL);
default:
@@ -69,21 +64,11 @@ export default {
},
weekStart() {
return luxon.Info.getStartOfWeek(this.date)%7;
},
rangeConfig() {
if (this.$props.mode === "list") {
return { autoRange: listLength - 1 };
} else if (this.$props.mode === "range") {
return true;
} else {
return false;
}
},
}
},
methods: {
update(value) {
let date;
let rangeLength;
switch (this.mode) {
case "month":
value.month++;
@@ -93,18 +78,13 @@ export default {
case "week":
date = luxon.DateTime.fromJSDate(value[0]).setZone(this.timezone, { keepLocalTime: true }).setLocale(this.locale);
break;
case "range":
date = luxon.DateTime.fromJSDate(value[0]).setZone(this.timezone, { keepLocalTime: true }).setLocale(this.locale);
let endDate = luxon.DateTime.fromJSDate(value[1]).setZone(this.timezone, { keepLocalTime: true }).setLocale(this.locale);
rangeLength = Math.floor(endDate.diff(date, "days").toObject().days) + 1;
break;
case "day":
date = luxon.DateTime.fromJSDate(value).setZone(this.timezone, { keepLocalTime: true }).setLocale(this.locale);
break;
default:
return; // Don't update if the value is invalid!
}
this.$emit("update:date", { date, rangeLength });
this.$emit("update:date", date);
},
weekNumbers(date) {
return luxon.DateTime.fromJSDate(date, { locale: this.locale }).localWeekNumber;
@@ -117,7 +97,7 @@ export default {
:format="() => title"
:month-picker="mode == 'month'"
:week-picker="mode == 'week'"
:range="rangeConfig"
:range="mode == 'list' ? { autoRange: listLength - 1 } : false"
:text-input="mode == 'day'"
:week-start="weekStart"
:week-numbers="{ type: weekNumbers }"
@@ -26,11 +26,7 @@ export default {
}
return true;
}
},
alignItemsClassSuffix: {
type: String,
default: "end",
},
}
},
computed: {
sanitizedTimestamps() {
@@ -53,7 +49,7 @@ export default {
}
},
template: `
<div class="fhc-calendar-base-label-time" :class="'align-items-' + $props.alignItemsClassSuffix">
<div class="fhc-calendar-base-label-time">
<span v-if="start">{{ start }}</span>
<span v-if="end">-</span>
<span v-if="end">{{ end }}</span>
+15 -36
View File
@@ -9,21 +9,13 @@ import ModeDay from './Mode/Day.js';
import ModeWeek from './Mode/Week.js';
import ModeMonth from './Mode/Month.js';
import ModeList from './Mode/List.js';
import ModeRange from './Mode/Range.js';
export default {
name: "CalendarLvPlan",
components: {
FhcCalendar
},
inject: {
isMobile: {
default: false,
},
rangeLength: {
default: 30,
}
},
inject: ["isMobile"],
props: {
date: {
type: [Date, String, Number, luxon.DateTime],
@@ -36,11 +28,7 @@ export default {
getPromiseFunc: {
type: Function,
required: true
},
shouldIncludeRangeMode: {
type: Boolean,
default: false,
},
}
},
provide() {
return {
@@ -80,37 +68,31 @@ export default {
backgrounds() {
let now = luxon.DateTime.now().setZone(this.timezone);
if (this.mode == 'Month') {
if (this.mode == 'Month')
return [
{
class: 'background-past',
end: now.startOf('day')
}
];
} else if (this.mode == 'Range') {
return [];
} else {
return [
{
class: 'background-past',
end: now,
label: now.startOf('minute').toISOTime({ suppressSeconds: true, includeOffset: false })
}
];
}
return [
{
class: 'background-past',
end: now,
label: now.startOf('minute').toISOTime({ suppressSeconds: true, includeOffset: false })
}
];
},
modes() {
let modes = {
day: Vue.markRaw(ModeDay),
month: Vue.markRaw(ModeMonth),
};
if (!this.isMobile) {
modes.week = Vue.markRaw(ModeWeek);
if (this.$props.shouldIncludeRangeMode) {
modes.range = Vue.markRaw(ModeRange);
}
} else {
if (this.isMobile) {
modes.list = Vue.markRaw(ModeList);
} else {
modes.week = Vue.markRaw(ModeWeek);
}
return modes;
@@ -181,7 +163,7 @@ export default {
:backgrounds="backgrounds"
:time-grid="teachingunits"
show-btns
@update:date="(newDate, newMode, newRangeLength) => $emit('update:date', newDate, newMode, newRangeLength)"
@update:date="(newDate, newMode) => $emit('update:date', newDate, newMode)"
@update:mode="(newMode, newDate) => $emit('update:mode', newMode, newDate)"
@update:range="updateRange"
>
@@ -192,9 +174,6 @@ export default {
>
{{ $p.t('lehre/noLvFound') }}
</div>
<div v-else-if="!renderers || !renderers[event.type]" class="placeholder-glow">
<span class="placeholder col-12"></span>
</div>
<div
v-else
:class="'event-type-' + event.type + ' ' + mode + 'PageContainer'"
-122
View File
@@ -1,122 +0,0 @@
import BaseSlider from "../Base/Slider.js";
import RangeView from "./Range/View.js";
import FormInput from "../../Form/Input.js";
export default {
name: "ModeRange",
components: {
BaseSlider,
RangeView,
FormInput,
},
inject: {
rangeLength: {
default: 30,
},
rangeViewPresets: {
default: {},
},
},
props: {
currentDate: {
type: luxon.DateTime,
required: true,
},
},
emits: ["update:currentDate", "update:range", "click", "requestModalOpen"],
data() {
return {
selectedRangePreset: null,
};
},
computed: {
range() {
let first = this.$props.currentDate;
let last = first.plus({ days: this.rangeLength });
return luxon.Interval.fromDateTimes(first, last);
},
},
watch: {
currentDate() {
this.$emit("update:range", this.range);
},
rangeLength() {
this.$emit("update:range", this.range);
},
selectedRangePreset() {
if (!this.selectedRangePreset) return;
const preset = this.rangeViewPresets.presets.find(
(preset) => preset.name === this.selectedRangePreset,
);
if (!preset) return;
this.$emit("update:date", {
date: preset.startDate,
rangeLength:
preset.endDate.diff(preset.startDate, "days").days + 1,
});
this.selectedRangePreset = null;
},
},
methods: {
viewAttrs() {
const day = this.$props.currentDate.startOf("day");
const rangeLength = parseInt(this.rangeLength);
return { ...this.$attrs, day, rangeLength };
},
handleClickDefaults(evt) {
switch (evt.detail.source) {
case "day":
// default: Set current-date
this.$emit("update:currentDate", {
date: evt.detail.value,
});
break;
case "event":
// default: Request Modal
this.$emit("requestModalOpen", { event: evt.detail.value });
break;
}
},
},
mounted() {
this.$emit("update:range", this.range);
},
template: /*html*/ `
<div
class="fhc-calendar-mode-range flex-grow-1 position-relative d-flex flex-column"
@cal-click-default.capture="handleClickDefaults"
>
<div
v-if="rangeViewPresets?.presets?.length"
id="rangePresetSelector"
class="w-100 d-flex flex-row gap-2 justify-content-center align-items-center py-2"
>
<span>{{ rangeViewPresets.label }}</span>
<form-input
name="rangePresetSelector"
type="select"
v-model="selectedRangePreset"
>
<option
v-for="preset in rangeViewPresets.presets"
:key="preset.name"
:value="preset.name"
>
{{ preset.name }}
</option>
</form-input>
</div>
<div class="flex-grow-1">
<base-slider ref="slider" v-slot="slot">
<range-view ref="view" v-bind="viewAttrs()">
<template v-slot="slot"><slot v-bind="slot" mode="range" /></template>
</range-view>
</base-slider>
</div>
</div>
`,
};
@@ -1,88 +0,0 @@
import CalendarGrid from '../../Base/Grid.js';
import LabelDay from '../../Base/Label/Day.js';
import LabelDow from '../../Base/Label/Dow.js';
import LabelTime from '../../Base/Label/Time.js';
export default {
name: "RangeView",
components: {
CalendarGrid,
LabelDay,
LabelDow,
LabelTime
},
inject: {
timeGrid: "timeGrid",
timezone: "timezone"
},
props: {
day: {
type: luxon.DateTime,
required: true
},
collapseEmptyDays: Boolean,
rangeLength: Number,
},
computed: {
axisMain() {
return Array.from({ length: this.$props.rangeLength }, (e, i) => this.$props.day.plus({ days: i }));
},
axisParts() {
if (this.timeGrid) {
// create {start, end} array
return this.timeGrid.map(tu => {
return {
start: luxon.Duration.fromISOTime(tu.start),
end: luxon.Duration.fromISOTime(tu.end)
};
});
} else {
// create 07:00-23:00
return Array.from({ length: 17 }, (e, i) => luxon.Duration.fromObject({ hours: i + 7 }));
}
}
},
methods: {
isToday(date) {
return date.hasSame(luxon.DateTime.now().setZone(this.timezone), 'day');
}
},
template: /* html */`
<div class="fhc-calendar-mode-range-view h-100">
<calendar-grid
ref="grid"
:axis-main="axisMain"
:axis-parts="axisParts"
:axis-main-collapsible="collapseEmptyDays"
:snap-to-grid="!!timeGrid"
:flip-axis="true"
:arePartHeadersRepeated="true"
all-day-events
>
<template #main-header="{ date }">
<div :class="{ today: isToday(date) }">
<label-dow
v-bind="{ date }"
@cal-click="evt => evt.detail.source = 'day'"
/>
<label-day
v-bind="{ date }"
/>
</div>
</template>
<template #part-header="{ part }">
<label-time v-bind="{ part }" :alignItemsClassSuffix="'center'" />
</template>
<template #part-header-repeat="{ part }">
<label-time v-bind="{ part }" :alignItemsClassSuffix="'center'" />
</template>
<template #event="slot">
<div v-if="slot.event.type == 'loading'" class="placeholder-glow h-100 opacity-50">
<span class="placeholder w-100 h-100" />
</div>
<slot v-else v-bind="slot" />
</template>
</calendar-grid>
</div>
`
}
-3
View File
@@ -79,9 +79,6 @@ export default {
>
{{ $p.t('lehre/noLvFound') }}
</div>
<div v-else-if="!renderers || !renderers[event.type]" class="placeholder-glow">
<span class="placeholder col-12"></span>
</div>
<component
v-else-if="mode == 'eventheader'"
:is="renderers[event.type]?.modalTitle"
+6 -46
View File
@@ -2,7 +2,6 @@ import FhcCalendar from "../../Calendar/LvPlan.js";
import ApiLvPlan from '../../../api/factory/lvPlan.js';
import ApiAuthinfo from '../../../api/factory/authinfo.js';
import ApiStudiensemester from '../../../api/factory/studiensemester.js';
export const DEFAULT_MODE_LVPLAN_DESKTOP = "Week";
export const DEFAULT_MODE_LVPLAN_MOBILE = "List";
@@ -24,23 +23,9 @@ export default {
isMitarbeiter: false,
isStudent: false,
timezone: FHC_JS_DATA_STORAGE_OBJECT.timezone,
semesterRangePresets: {
label: null,
presets: [],
},
};
},
inject: ["isMobile"],
provide() {
return {
rangeLength: Vue.computed(() => {
if (!this.$route.params.range_length) return 30;
else if (this.$route.params.range_length > 365) return 365;
else return this.$route.params.range_length;
}),
rangeViewPresets: Vue.computed(() => this.semesterRangePresets)
};
},
computed:{
currentDay() {
if (!this.propsViewData?.focus_date || isNaN(new Date(this.propsViewData?.focus_date)))
@@ -49,11 +34,7 @@ export default {
},
currentMode() {
let validModes = ["day", "month"];
if (!this.isMobile) {
validModes.push("week", "range");
} else {
validModes.push("list");
}
validModes.push(this.isMobile ? "list" : "week");
const defaultMode = this.isMobile
? DEFAULT_MODE_LVPLAN_MOBILE
@@ -113,10 +94,10 @@ export default {
},
},
methods: {
handleChangeDate(day, newMode, rangeLength) {
return this.handleChangeMode(newMode, day, rangeLength);
handleChangeDate(day, newMode) {
return this.handleChangeMode(newMode, day);
},
handleChangeMode(newMode, day, range_length = null) {
handleChangeMode(newMode, day) {
const mode = newMode[0].toUpperCase() + newMode.slice(1)
const focus_date = day.toISODate();
@@ -124,8 +105,7 @@ export default {
name: "MyLvPlan",
params: {
mode,
focus_date,
range_length,
focus_date
}
});
},
@@ -154,32 +134,13 @@ export default {
this.isMitarbeiter = authInfo.isMitarbeiter;
this.isStudent = authInfo.isStudent;
},
async fetchSemesters() {
const semestersResponse = await this.$api.call(ApiStudiensemester.getAll());
if (semestersResponse.meta.status === "success") {
this.semesterRangePresets = {
label: "View specific semester",
presets: semestersResponse.data.map((semester) => {
let startDate = luxon.DateTime.fromISO(semester.start);
let endDate = luxon.DateTime.fromISO(semester.ende);
return {
startDate,
endDate,
name: semester.studiensemester_kurzbz,
description: semester.bezeichnung,
};
})
};
}
},
},
async created() {
await this.fetchAuthInfo();
await this.fetchSemesters();
},
template: /*html*/`
<div class="cis-lvplan-personal d-flex flex-column h-100">
<h2 id="cis-lvplan-personal-heading">
<h2>
{{ $p.t('lehre/stundenplan') }}
<span v-if="studiensemester_kurzbz" class="ps-3">
{{ studiensemester_kurzbz }}
@@ -193,7 +154,6 @@ export default {
:get-promise-func="getPromiseFunc"
:date="currentDay"
:mode="currentMode"
:shouldIncludeRangeMode="true"
@update:date="handleChangeDate"
@update:mode="handleChangeMode"
@update:range="updateRange"
@@ -206,8 +206,6 @@ export default {
});
this.lehrveranstaltungen.forEach((lehrveranstaltung)=>{
// a lehrveranstaltung without a modul has no children
if(!Array.isArray(lehrveranstaltung.lehrveranstaltungen)) return;
lehrveranstaltung.lehrveranstaltungen.sort((lv1,lv2)=>{
if (lv1.bezeichnung.toLowerCase() > lv2.bezeichnung.toLowerCase()) {
return 1;
@@ -395,16 +393,6 @@ export default {
</ul>
</div>
</div>
<div class="card" v-else-if="!Array.isArray(lehrveranstaltung.lehrveranstaltungen)">
<div class="card-body">
<ul class="list-group list-group-flush">
<li class="d-flex list-group-item">
<a class="fhc-link-color d-block me-auto" href="#" @click="openLvUebersicht(lehrveranstaltung)">{{isGermanLanguage ? lehrveranstaltung.bezeichnung : lehrveranstaltung.bezeichnung_english}}</a>
<p>{{lehrveranstaltung.lehrform_kurzbz}}</p>
</li>
</ul>
</div>
</div>
</template>
</div>
@@ -400,37 +400,32 @@ export default {
<div v-if="headerData.length == 1">
<div v-if="!isLoading" class="d-flex align-items-center gap-3">
<h2 class="h4 mt-2">
<h2 class="h4">
{{headerData[0].titelpre}}
{{headerData[0].vorname}}
{{headerData[0].nachname}}
<span v-if="headerData[0].titelpost">, </span>
{{headerData[0].titelpost}}
</h2>
<div class="d-flex border rounded align-items-center ps-1">
<core-tag ref="tagComponent"
v-if="tagsEnabled"
:endpoint="tagEndpoint"
:values="prestudentIds"
:show-hover="true"
@added="addedTag"
@deleted="deletedTag"
@updated="updatedTag"
zuordnung_typ="prestudent_id"
></core-tag>
<div
role="button"
v-if="tagsEnabled"
@click="rebuildPrestudentTags"
class="btn btn-outline btn-sm btn-hover m-1"
:title="$p.t('tag','rebuild_tags') + ' ' + currentSemester"
>
<i class="fa-solid fa-refresh pe-1"></i>
<span>{{currentSemester}}</span>
</div>
<core-tag ref="tagComponent"
v-if="tagsEnabled"
:endpoint="tagEndpoint"
:values="prestudentIds"
@added="addedTag"
@deleted="deletedTag"
@updated="updatedTag"
zuordnung_typ="prestudent_id"
></core-tag>
<div
role="button"
v-if="tagsEnabled"
@click="rebuildPrestudentTags"
class="btn btn-outline btn-light mb-1"
:title="'Automatische Tags fuer ' + currentSemester + ' neu laden'"
>
<i class="fa-solid fa-refresh pe-1"></i>
<span>{{currentSemester}}</span>
</div>
<h6 v-if="headerData[0].unruly" class="badge" :class="'bg-unruly rounded-0'"><strong>unruly</strong></h6>
</div>
<div v-else class="d-flex align-items-center gap-3">
+171 -114
View File
@@ -4,7 +4,7 @@ import {CoreFilterCmpt} from "../filter/Filter.js";
import BsModal from "../Bootstrap/Modal.js";
import FormForm from '../Form/Form.js';
import FormInput from '../Form/Input.js';
import PvAutoComplete from "../../../../index.ci.php/public/js/components/primevue/autocomplete/autocomplete.esm.min.js";
export default {
components: {
@@ -13,7 +13,8 @@ export default {
FormUploadDms,
FormForm,
FormInput,
BsModal
BsModal,
PvAutoComplete
},
props: {
endpoint: {
@@ -52,7 +53,6 @@ export default {
multiupload: true,
mitarbeiter: [],
filteredMitarbeiter: [],
zwischenvar: '',
editorInitialized: false,
editor: null,
notizData: {
@@ -85,7 +85,11 @@ export default {
showId: false,
showLastupdate: false
},
currentVerfasserUid: null
selectedBearbeiter: null,
selectedVerfasser: null,
abortController: {
mitarbeiter: null
}
}
},
computed: {
@@ -297,6 +301,15 @@ export default {
},
actionEditNotiz(notiz_id) {
this.loadNotiz(notiz_id).then(() => {
this.selectedBearbeiter = {
label: this.getLabelUid(this.notizData.perb_titelpre, this.notizData.perb_nachname, this.notizData.perb_vorname, this.notizData.perb_titelpost, this.notizData.perb_uid),
uid: this.notizData.perb_uid
}
this.selectedVerfasser = {
label: this.getLabelUid(this.notizData.perv_titelpre, this.notizData.perv_nachname, this.notizData.perv_vorname, this.notizData.perv_titelpost, this.notizData.perv_uid),
uid: this.notizData.perv_uid
}
if (this.notizen.notiz_id) {
this.notizData.typeId = this.typeId;
this.notizData.titel = this.notizen.titel;
@@ -308,8 +321,13 @@ export default {
this.notizData.bis = this.notizen.ende;
this.notizData.document = this.notizen.dms_id;
this.notizData.erledigt = this.notizen.erledigt;
this.notizData.intBearbeiter = this.notizen.bearbeiter_uid;
this.notizData.bearbeiter = this.notizen.bearbeiter_uid;
//this.notizData.bearbeiter_uid = this.notizen.bearbeiter_uid;
//for saving
//this.notizData.intBearbeiter = this.notizen.bearbeiter_uid;
//this.notizData.bearbeiter = this.notizen.bearbeiter_uid;
//new way
this.notizData.bearbeiter = this.selectedBearbeiter.uid;
this.notizData.verfasser = this.selectedVerfasser.uid;
}
})
.then(() => {
@@ -323,7 +341,9 @@ export default {
});
},
actionNewNotiz() {
console.log("auf plus");
this.resetFormData();
this.getDefaultVerfasser();
if(this.notizLayout == 'popupModal') {
this.$refs.NotizModal.show();
}
@@ -374,7 +394,6 @@ export default {
this.notizData = result.data;
this.notizData.typeId = this.typeId;
this.notizData.anhang = [];
this.currentVerfasserUid = result.data.verfasser_uid;
return result;
})
.catch(this.$fhcAlert.handleSystemError);
@@ -431,24 +450,54 @@ export default {
bearbeiter: null,
anhang: [],
};
this.currentVerfasserUid = this.uid
this.selectedBearbeiter = null;
this.selectedVerfasser = null;
},
getUid() {
getDefaultVerfasser() {
return this.$api
.call(this.endpoint.getUid())
.call(this.endpoint.getDefaultVerfasser())
.then(result => {
this.currentVerfasserUid = result.data;
this.uid = result.data;
const verfasser = result.data;
this.selectedVerfasser = {
label: this.getLabelUid(verfasser.titelpre, verfasser.nachname, verfasser.vorname, verfasser.titelpost, verfasser.mitarbeiter_uid),
uid: verfasser.uid
}
})
.catch(this.$fhcAlert.handleSystemError);
},
search(event) {
searchMitarbeiter(event) {
if (this.abortController.mitarbeiter) {
this.abortController.mitarbeiter.abort();
}
this.abortController.mitarbeiter = new AbortController();
return this.$api
.call(this.endpoint.getMitarbeiter(event.query))
.then(result => {
this.filteredMitarbeiter = result.data.retval;
//this.filteredMitarbeiter = result.data.retval;
this.filteredMitarbeiter = [];
for (let mitarbeiter of result.data.retval) {
this.filteredMitarbeiter.push(
{
label: this.getLabelUid(
mitarbeiter.titelpre,
mitarbeiter.nachname,
mitarbeiter.vorname,
mitarbeiter.titelpost,
mitarbeiter.mitarbeiter_uid
),
uid: mitarbeiter.mitarbeiter_uid
}
);
}
});
},
getLabelUid(titelpre, nachname, vorname, titelpost, uid) {
if (!nachname && !vorname && !uid)
return '';
return nachname + ' ' + vorname + (titelpre ? ' ' + titelpre : '') + (titelpost ? ' ' + titelpost : '') + (uid ? ' (' + uid + ')' : '');
},
initTinyMCE() {
const vm = this;
tinymce.init({
@@ -491,7 +540,7 @@ export default {
},
created() {
this.initializeShowVariables();
this.getUid();
this.getDefaultVerfasser();
},
async mounted() {
if (this.showTinyMce) {
@@ -512,18 +561,14 @@ export default {
}
}
},
//Watcher für autocomplete Bearbeiter und Verfasser
'notizData.intBearbeiter': {
handler(newVal) {
if (typeof newVal === 'object') {
this.notizData.bearbeiter = newVal.mitarbeiter_uid;
}
},
deep: true
},
id() {
this.reload();
},
selectedBearbeiter(newVal) {
this.notizData.bearbeiter = newVal?.uid || null;
},
selectedVerfasser(newVal) {
this.notizData.verfasser = newVal?.uid || null;
}
},
beforeUnmount() {
@@ -621,9 +666,21 @@ export default {
<div class="row mb-3">
<label for="bis" class="form-label col-sm-2">{{$p.t('notiz','verfasser')}}</label>
<div class="col-sm-3">
<input type="text" readonly="readonly" class="form-control" id="name" v-model="currentVerfasserUid">
</div>
<form-input
type="autocomplete"
container-class="col-3"
v-model="selectedVerfasser"
optionLabel="label"
optionValue="uid"
disabled
dropdown
forceSelection
name="bearbeiter"
:suggestions="filteredMitarbeiter"
@complete="searchMitarbeiter"
:min-length="3"
>
</form-input>
<label for="von" class="form-label col-sm-1">{{$p.t('global','gueltigVon')}}</label>
<div class="col-sm-3">
@@ -644,21 +701,20 @@ export default {
<div class="row mb-3">
<label for="bis" class="form-label col-sm-2">{{$p.t('notiz','bearbeiter')}}</label>
<div v-if="notizData.bearbeiter_uid" class="col-sm-3">
<input type="text" class="form-control" id="name" v-model="notizData.bearbeiter_uid">
</div>
<div v-else class="col-sm-3">
<form-input
type="autocomplete"
v-model="notizData.intBearbeiter"
:suggestions="filteredMitarbeiter"
@complete="search"
optionLabel="mitarbeiter"
minlength="3"
<form-input
type="autocomplete"
container-class="col-3"
v-model="selectedBearbeiter"
optionLabel="label"
optionValue="uid"
dropdown
forceSelection
name="bearbeiter"
:suggestions="filteredMitarbeiter"
@complete="searchMitarbeiter"
:min-length="3"
>
</form-input>
</div>
</form-input>
<label for="bis" class="form-label col-sm-1">{{$p.t('global','gueltigBis')}}</label>
@@ -816,35 +872,36 @@ export default {
</div>
<div class="row mb-3">
<form-input
<form-input
type="autocomplete"
container-class="col-6"
:label="$p.t('notiz', 'verfasser')"
type="text"
readonly="readonly"
v-model="currentVerfasserUid"
name="verfasser"
v-model="selectedVerfasser"
optionLabel="label"
optionValue="uid"
disabled
dropdown
forceSelection
name="bearbeiter"
:suggestions="filteredMitarbeiter"
@complete="searchMitarbeiter"
:min-length="3"
>
</form-input>
<form-input
v-if="notizData.bearbeiter_uid"
container-class="col-6"
:label="$p.t('notiz', 'bearbeiter')"
v-model="notizData.bearbeiter_uid"
minlength="3"
>
</form-input>
<form-input
v-else
container-class="col-6"
:label="$p.t('notiz', 'bearbeiter')"
type="autocomplete"
v-model="notizData.intBearbeiter"
:suggestions="filteredMitarbeiter"
@complete="search"
optionLabel="mitarbeiter"
minlength="3"
container-class="col-6"
:label="$p.t('notiz', 'bearbeiter')"
v-model="selectedBearbeiter"
optionLabel="label"
optionValue="uid"
dropdown
forceSelection
name="bearbeiter"
:suggestions="filteredMitarbeiter"
@complete="searchMitarbeiter"
:min-length="3"
>
</form-input>
</div>
@@ -969,37 +1026,38 @@ export default {
<div class="row mb-3">
<form-input
<form-input
type="autocomplete"
container-class="col-6"
:label="$p.t('notiz', 'verfasser')"
type="text"
readonly="readonly"
v-model="currentVerfasserUid"
name="verfasser"
v-model="selectedVerfasser"
optionLabel="label"
optionValue="uid"
disabled
dropdown
forceSelection
name="bearbeiter"
:suggestions="filteredMitarbeiter"
@complete="searchMitarbeiter"
:min-length="3"
>
</form-input>
<form-input
v-if="notizData.bearbeiter_uid"
container-class="col-6"
:label="$p.t('notiz', 'bearbeiter')"
v-model="notizData.bearbeiter_uid"
minlength="3"
>
</form-input>
<form-input
v-else
container-class="col-6"
:label="$p.t('notiz', 'bearbeiter')"
type="autocomplete"
v-model="notizData.intBearbeiter"
:suggestions="filteredMitarbeiter"
@complete="search"
optionLabel="mitarbeiter"
minlength="3"
container-class="col-6"
:label="$p.t('notiz', 'bearbeiter')"
v-model="selectedBearbeiter"
optionLabel="label"
optionValue="uid"
dropdown
forceSelection
name="bearbeiter"
:suggestions="filteredMitarbeiter"
@complete="searchMitarbeiter"
:min-length="3"
>
</form-input>
</form-input>
</div>
<div class="row mb-3">
@@ -1140,44 +1198,43 @@ export default {
>
</form-input>
</div>
<div class="row mb-3">
<form-input
<form-input
type="autocomplete"
container-class="col-6"
:label="$p.t('notiz', 'verfasser')"
type="text"
readonly="readonly"
v-model="currentVerfasserUid"
name="verfasser"
>
</form-input>
<form-input
v-if="notizData.bearbeiter_uid"
container-class="col-6"
:label="$p.t('notiz', 'bearbeiter')"
v-model="notizData.bearbeiter_uid"
v-model="selectedVerfasser"
optionLabel="label"
optionValue="uid"
disabled
dropdown
forceSelection
name="bearbeiter"
minlength="3"
:suggestions="filteredMitarbeiter"
@complete="searchMitarbeiter"
:min-length="3"
>
</form-input>
<form-input
v-else
container-class="col-6"
:label="$p.t('notiz', 'bearbeiter')"
type="autocomplete"
v-model="notizData.intBearbeiter"
:suggestions="filteredMitarbeiter"
@complete="search"
optionLabel="mitarbeiter"
container-class="col-6"
:label="$p.t('notiz', 'bearbeiter')"
v-model="selectedBearbeiter"
optionLabel="label"
optionValue="uid"
dropdown
forceSelection
name="bearbeiter"
minlength="3"
:suggestions="filteredMitarbeiter"
@complete="searchMitarbeiter"
:min-length="3"
>
</form-input>
</form-input>
</div>
<div class="row mb-3">
<div class="col-2 pt-4 d-flex align-items-center">
<form-input
@@ -553,12 +553,8 @@ export default {
}
if (!this.formData.akadgrad_id && this.arrAkadGrad.length > 0) {
const akadGradByGeschlecht = this.arrAkadGrad.find((item) => {
return item.geschlecht === this.student.geschlecht;
});
if(akadGradByGeschlecht)
this.formData.akadgrad_id = akadGradByGeschlecht.akadgrad_id;
if(this.student.geschlecht == 'w')
this.formData.akadgrad_id = this.arrAkadGrad[1].akadgrad_id;
else
this.formData.akadgrad_id = this.arrAkadGrad[0].akadgrad_id;
}
@@ -317,7 +317,6 @@ export default {
"$p.user_language.value"(n, o) {
if (n !== o && o !== undefined && this.$refs.table.tableBuilt) {
this.translateTabulator();
this.$reloadList();
}
},
"tagFilterState.selectedOptions": {
@@ -843,39 +842,35 @@ export default {
</div>
</template>
<template #tags>
<div class="d-flex border rounded align-items-center ps-1">
<core-tag ref="tagComponent"
v-if="tagsEnabled"
:endpoint="tagEndpoint"
:values="selectedColumnValues"
@added="addedTag"
@deleted="deletedTag"
@updated="updatedTag"
zuordnung_typ="prestudent_id"
show-hover
></core-tag>
<template #actions>
<core-tag ref="tagComponent"
v-if="tagsEnabled"
:endpoint="tagEndpoint"
:values="selectedColumnValues"
@added="addedTag"
@deleted="deletedTag"
@updated="updatedTag"
zuordnung_typ="prestudent_id"
></core-tag>
<button
v-if="!selected.length"
class="btn btn-outline btn-sm m-1 btn-hover"
@click="rebuild(selected)"
:title="$p.t('tag','rebuild_tags') + ' Stg ' + currentSemester"
>
<i class="fa-solid fa-refresh pe-1"></i> STG
</button>
<button
v-else
class="btn btn-outline btn-sm m-1 btn-hover"
@click="rebuild(selected)"
:title="$p.t('tag','rebuild_tags') + ' ' + $p.t('ui','selection') + ' ' + currentSemester"
>
<i class="fa-solid fa-refresh pe-1"></i> {{selected.length}}
</button>
</div>
</template>
<button
v-if="!selected.length"
class="btn btn-outline btn-light mb-1"
@click="rebuild(selected)"
:title="$p.t('tag','rebuild_tags') + ' Stg ' + currentSemester"
>
<i class="fa-solid fa-refresh pe-1"></i> STG
</button>
<button
v-else
class="btn btn-outline btn-light mb-1"
@click="rebuild(selected)"
:title="$p.t('tag','rebuild_tags') + ' ' + $p.t('ui','selection') + ' ' + currentSemester"
>
<i class="fa-solid fa-refresh pe-1"></i> {{selected.length}}
</button>
<template v-if="filter.length || headerFilterActive">
<template v-if="filter.length || headerFilterActive">
<div class="d-flex justify-content-center align-items-center gap-2 ps-4 position-absolute start-50 translate-middle-x">
<p class="text-danger mb-0">
<strong>{{$p.t('filter','filterActive')}}</strong>
@@ -889,6 +884,8 @@ export default {
<span class="fa-solid fa-filter-circle-xmark"></span>
</button>
</div>
</template>
</template>
<template #filter>
@@ -898,7 +895,6 @@ export default {
</div>
</div>
</template>
</core-filter-cmpt>
</div>
<list-new ref="new" :studiengang-kz="studiengangKz" :studiensemester-kurzbz="studiensemesterKurzbz"></list-new>
+1 -6
View File
@@ -28,10 +28,6 @@ export default {
confirmLimit: {
type: Number,
default: 20
},
showHover: {
type: Boolean,
default: false
}
},
data() {
@@ -232,8 +228,7 @@ export default {
<span :title="values.length === 0 ? 'Bitte Zeilen markieren' : ''">
<button @mouseover="showList = true"
:disabled="!values || values.length === 0"
class="btn btn-sm"
:class="{'btn-hover': showHover}">
class="btn btn-sm">
<i class="fa-solid fa-tag fa-xl"></i>
</button>
</span>
+1 -2
View File
@@ -1094,7 +1094,7 @@ export const CoreFilterCmpt = {
<div :id="'filterCollapsables' + idExtra">
<div class="d-flex flex-row justify-content-between flex-wrap">
<div v-if="newBtnShow || reload || $slots.search || $slots.actions || $slots.tags" class="d-flex gap-2 align-items-baseline flex-wrap">
<div v-if="newBtnShow || reload || $slots.search || $slots.actions" class="d-flex gap-2 align-items-baseline flex-wrap">
<button v-if="newBtnShow" class="btn btn-primary" :class="newBtnClass" :title="newBtnLabel ? undefined : 'New'" :aria-label="newBtnLabel ? undefined : 'New'" @click="$emit('click:new', $event)" :disabled="newBtnDisabled">
<span class="fa-solid fa-plus" aria-hidden="true"></span>
{{ newBtnLabel }}
@@ -1107,7 +1107,6 @@ export const CoreFilterCmpt = {
<span v-else id="selected-info-text"> Mit {{ selectedData.length }} ausgewählten:</span>
</span>
<slot name="actions" v-bind="{selected: tabulatorHasSelector ? selectedData : []}"></slot>
<slot name="tags"></slot>
<slot name="search"></slot>
</div>
<div class="d-flex gap-1 align-items-baseline flex-grow-1 justify-content-end">