Add removal of multiple weeks per validation period, and other minor changes

This commit is contained in:
Ivymaster
2026-05-11 18:45:44 +02:00
parent fe5931e6c7
commit 6560b27de9
17 changed files with 432 additions and 292 deletions
@@ -34,8 +34,8 @@ class ClassScheduleApi extends FHCAPI_Controller
'deleteClassTimeSlotValidityPeriod' => array('lehre/unterrichtszeiten_gk:rw'),
'getClassTimeSlotsForValidityPeriod' => array('lehre/unterrichtszeiten_gk:r'),
'createClassTimeSlotsForValidityPeriod' => array('lehre/unterrichtszeiten_gk:r'),
'editClassTimeSlotsForValidityPeriod' => array('lehre/unterrichtszeiten_gk:r'),
'deleteClassTimeSlotsForValidityPeriodPerGroup' => array('lehre/unterrichtszeiten_gk:r'),
'updateClassTimeSlotsForValidityPeriod' => array('lehre/unterrichtszeiten_gk:r'),
'deleteClassTimeSlotsForValidityPeriod' => array('lehre/unterrichtszeiten_gk:r'),
'getAllClassScheduleTypes' => array('lehre/unterrichtszeiten_typ:r'),
'createClassTimeSlotType' => array('lehre/unterrichtszeiten_typ:rw'),
'updateClassTimeSlotType' => array('lehre/unterrichtszeiten_typ:rw'),
@@ -135,6 +135,9 @@ class ClassScheduleApi extends FHCAPI_Controller
$class_time_slot_validity_period_res = $this->ClassTimeSlotValidityPeriodModel->load($classTimeSlotValidityPeriodId);
$class_time_slot_validity_period_res = $this->getDataOrTerminateWithError($class_time_slot_validity_period_res);
if (!$class_time_slot_validity_period_res || count($class_time_slot_validity_period_res) === 0) {
$this->terminateWithError($this->p->t('ui', 'classTimeSlotValidityPeriodNotFound'));
}
if (!$this->isUserEntitledForOrganizationalUnit($class_time_slot_validity_period_res[0]->oe_kurzbz, 'lehre/unterrichtszeiten_gk')) {
$this->terminateWithError($this->p->t('ui', 'keineBerechtigung'));
@@ -154,8 +157,7 @@ class ClassScheduleApi extends FHCAPI_Controller
'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => $this->p->t('ui', 'field_organizationalUnit')]),
'max_length' => $this->p->t('ui', 'error_fieldMaxLength', ['field' => $this->p->t('ui', 'field_organizationalUnit'), 'max' => 32])
]);
$this->form_validation->set_rules('semester', 'Semester', 'required|is_natural_no_zero|less_than_equal_to[8]', [
'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => $this->p->t('ui', 'field_semester')]),
$this->form_validation->set_rules('semester', 'Semester', 'is_natural_no_zero|less_than_equal_to[8]', [
'is_natural_no_zero' => $this->p->t('ui', 'error_fieldInvalid', ['field' => $this->p->t('ui', 'field_semester')]),
'less_than_equal_to' => $this->p->t('ui', 'error_fieldMaxValue', ['field' => $this->p->t('ui', 'field_semester'), 'max' => 8])
]);
@@ -202,8 +204,7 @@ class ClassScheduleApi extends FHCAPI_Controller
'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => $this->p->t('ui', 'field_organizationalUnit')]),
'max_length' => $this->p->t('ui', 'error_fieldMaxLength', ['field' => $this->p->t('ui', 'field_organizationalUnit'), 'max' => 32])
]);
$this->form_validation->set_rules('semester', 'Semester', 'required|is_natural_no_zero|less_than_equal_to[8]', [
'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => $this->p->t('ui', 'field_semester')]),
$this->form_validation->set_rules('semester', 'Semester', 'is_natural_no_zero|less_than_equal_to[8]', [
'is_natural_no_zero' => $this->p->t('ui', 'error_fieldInvalid', ['field' => $this->p->t('ui', 'field_semester')]),
'less_than_equal_to' => $this->p->t('ui', 'error_fieldMaxValue', ['field' => $this->p->t('ui', 'field_semester'), 'max' => 8])
]);
@@ -265,7 +266,13 @@ class ClassScheduleApi extends FHCAPI_Controller
public function getClassTimeSlotsForValidityPeriod($classTimeSlotValidityPeriodId)
{
$validityPeriodResult = $this->ClassTimeSlotValidityPeriodModel->loadWhere(['unterrichtszeitengueltigkeit_id' => $classTimeSlotValidityPeriodId]);
$validityPeriod = $this->getDataOrTerminateWithError($validityPeriodResult)[0];
$validityPeriodData = $this->getDataOrTerminateWithError($validityPeriodResult);
if (!$validityPeriodData || count($validityPeriodData) === 0) {
$this->terminateWithError($this->p->t('ui', 'classTimeSlotValidityPeriodNotFound'));
}
$validityPeriod = $validityPeriodData[0];
if (!$this->isUserEntitledForOrganizationalUnit($validityPeriod->oe_kurzbz, 'lehre/unterrichtszeiten_gk')) {
$this->terminateWithError($this->p->t('ui', 'keineBerechtigung'));
}
@@ -289,11 +296,8 @@ class ClassScheduleApi extends FHCAPI_Controller
$this->db->trans_start();
$timeSlotGroupIdentifier = uniqid();
foreach ($this->input->post('unterrichtszeiten') as $timeSlot) {
$result = $this->ClassTimeSlotModel->insert([
'unterrichtszeit_gruppe_identifikator' => $timeSlotGroupIdentifier,
'wochentag' => $timeSlot['wochentag'],
'uhrzeit_von' => $timeSlot['startTime'],
'uhrzeit_bis' => $timeSlot['endTime'],
@@ -312,7 +316,7 @@ class ClassScheduleApi extends FHCAPI_Controller
$this->terminateWithSuccess(true);
}
public function editClassTimeSlotsForValidityPeriod($classTimeSlotValidityPeriodId)
public function updateClassTimeSlotsForValidityPeriod($classTimeSlotValidityPeriodId)
{
$this->form_validation->set_rules('unterrichtszeiten', 'Class Time Slots', 'callback_validate_items_in_class_time_slots');
if($this->form_validation->run() == FALSE) $this->terminateWithValidationErrors($this->form_validation->error_array());
@@ -325,11 +329,19 @@ class ClassScheduleApi extends FHCAPI_Controller
$this->db->trans_start();
$timeSlotGroupIdentifier = uniqid();
$currentTimeSlotsResult = $this->ClassTimeSlotModel->loadWhere(['unterrichtszeitengueltigkeit_id' => $classTimeSlotValidityPeriodId]);
$currentTimeSlots = $this->getDataOrTerminateWithError($currentTimeSlotsResult);
$currentTimeSlotIds = array_column($currentTimeSlots, 'unterrichtszeit_id');
$removedTimeSlotIds = array_values(array_diff($currentTimeSlotIds, array_column($this->input->post('unterrichtszeiten'), 'id')));
if (count($removedTimeSlotIds) > 0) {
$query = 'DELETE FROM lehre.tbl_unterrichtszeiten WHERE unterrichtszeit_id IN ?';
$result = $this->db->query($query, [ $removedTimeSlotIds ]);
}
foreach ($this->input->post('unterrichtszeiten') as $timeSlot) {
$data = [
'unterrichtszeit_gruppe_identifikator' => $timeSlotGroupIdentifier,
'wochentag' => $timeSlot['wochentag'],
'uhrzeit_von' => $timeSlot['startTime'],
'uhrzeit_bis' => $timeSlot['endTime'],
@@ -354,7 +366,7 @@ class ClassScheduleApi extends FHCAPI_Controller
$this->terminateWithSuccess(true);
}
public function deleteClassTimeSlotsForValidityPeriodPerGroup($classTimeSlotValidityPeriodId, $groupIdentifikator)
public function deleteClassTimeSlotsForValidityPeriod($classTimeSlotValidityPeriodId)
{
$validityPeriodResult = $this->ClassTimeSlotValidityPeriodModel->loadWhere(['unterrichtszeitengueltigkeit_id' => $classTimeSlotValidityPeriodId]);
$validityPeriod = $this->getDataOrTerminateWithError($validityPeriodResult)[0];
@@ -364,7 +376,7 @@ class ClassScheduleApi extends FHCAPI_Controller
$this->db->trans_start();
$result = $this->ClassTimeSlotModel->delete(['unterrichtszeitengueltigkeit_id'=> $classTimeSlotValidityPeriodId, 'unterrichtszeit_gruppe_identifikator' => $groupIdentifikator]);
$result = $this->ClassTimeSlotModel->delete(['unterrichtszeitengueltigkeit_id'=> $classTimeSlotValidityPeriodId]);
if (isError($result)) {
$this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
}
@@ -439,7 +451,8 @@ class ClassScheduleApi extends FHCAPI_Controller
if($this->form_validation->run() == FALSE) $this->terminateWithValidationErrors($this->form_validation->error_array());
$this->db->trans_start();
$parsedClassTimeSlotTypeId = urldecode($classTimeSlotTypeId);
$descriptions = $this->input->post('descriptions');
$pgArray = $this->arrayToPgArray($descriptions);
@@ -451,7 +464,7 @@ class ClassScheduleApi extends FHCAPI_Controller
$this->input->post('isActive'),
date('c'),
getAuthUid(),
$classTimeSlotTypeId,
$parsedClassTimeSlotTypeId,
]);
$this->db->trans_complete();
@@ -461,14 +474,15 @@ class ClassScheduleApi extends FHCAPI_Controller
public function deleteClassTimeSlotType($classTimeSlotTypeId)
{
$isClassTimeSlotDeletable = true;
$validityPeriodResult = $this->ClassTimeSlotValidityPeriodModel->loadWhere(['unterrichtszeitentyp_kurzbz' => $classTimeSlotTypeId]);
$parsedClassTimeSlotTypeId = urldecode($classTimeSlotTypeId);
$validityPeriodResult = $this->ClassTimeSlotValidityPeriodModel->loadWhere(['unterrichtszeitentyp_kurzbz' => $parsedClassTimeSlotTypeId]);
$validityPeriod = $this->getDataOrTerminateWithError($validityPeriodResult);
if ($validityPeriod && count($validityPeriod) > 0) {
$isClassTimeSlotDeletable = false;
}
$classTimeSlotResult = $this->ClassTimeSlotModel->loadWhere(['unterrichtszeitentyp_kurzbz' => $classTimeSlotTypeId]);
$classTimeSlotResult = $this->ClassTimeSlotModel->loadWhere(['unterrichtszeitentyp_kurzbz' => $parsedClassTimeSlotTypeId]);
$classTimeSlot = $this->getDataOrTerminateWithError($classTimeSlotResult);
if ($classTimeSlot && count($classTimeSlot) > 0) {
$isClassTimeSlotDeletable = false;
@@ -477,9 +491,9 @@ class ClassScheduleApi extends FHCAPI_Controller
$this->db->trans_start();
if (!$isClassTimeSlotDeletable) {
$result = $this->ClassTimeSlotTypeModel->update($classTimeSlotTypeId, ['aktiv' => false, 'updateamum' => date('c'), 'updatevon' => getAuthUid()]);
$result = $this->ClassTimeSlotTypeModel->update($parsedClassTimeSlotTypeId, ['aktiv' => false, 'updateamum' => date('c'), 'updatevon' => getAuthUid()]);
} else {
$result = $this->ClassTimeSlotTypeModel->delete(['unterrichtszeitentyp_kurzbz' => $classTimeSlotTypeId]);
$result = $this->ClassTimeSlotTypeModel->delete(['unterrichtszeitentyp_kurzbz' => $parsedClassTimeSlotTypeId]);
}
if (isError($result)) {
$this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
@@ -492,10 +506,16 @@ class ClassScheduleApi extends FHCAPI_Controller
//------------------------------------------------------------------------------------------------------------------
// Private methods
private function arrayToPgArray(array $assoc) {
$flat = [];
$flat = [null, null];
foreach ($assoc as $assocItem) {
$flat[] = $assocItem['lang'] . ':' . $assocItem['value'];
if ($assocItem['lang'] === 'de') {
$flat[0] = $assocItem['value'];
} else if ($assocItem['lang'] === 'en') {
$flat[1] = $assocItem['value'];
} else {
$flat[] = $assocItem['value'];
}
}
$escaped = array_map(function ($v) {
@@ -554,7 +574,7 @@ class ClassScheduleApi extends FHCAPI_Controller
return false;
}
if (!in_array($timeSlot['wochentag'], [1, 2, 3, 4, 5, 6, 7])) {
if (!in_array($timeSlot['wochentag'], [1, 2, 3, 4, 5, 6, 0])) {
$this->form_validation->set_message(
'validate_items_in_class_time_slots',
$this->p->t('ui', 'error_fieldWeekdayInvalid', ['field' => $this->p->t('ui', 'field_classTimeSlot')])
@@ -0,0 +1,43 @@
<?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 ClassroomHourApi extends FHCAPI_Controller
{
public function __construct()
{
parent::__construct([
'getAllClassroomHours' => self::PERM_LOGGED,
]);
$this->load->model('ressource/Stunde_model', "ClassroomHourModel");
}
//------------------------------------------------------------------------------------------------------------------
// Public methods
public function getAllClassroomHours()
{
$this->ClassroomHourModel->addOrder('stunde', 'ASC');
$result = $this->ClassroomHourModel->load();
$this->terminateWithSuccess($this->getDataOrTerminateWithError($result));
}
}
@@ -31,8 +31,11 @@ class Studiensemester extends FHCAPI_Controller
'getStudySemestersByStudyPlanAndDates' => self::PERM_LOGGED,
)
);
// Load model StudiensemesterModel
$this->load->model('organisation/studiensemester_model', 'StudiensemesterModel');
$this->load->model('organisation/Studiengang_model', 'StudiengangModel');
$this->load->model('organisation/Studienordnung_model', 'StudienordnungModel');
$this->load->model('organisation/Studienplan_model', 'StudienplanModel');
}
/**
@@ -171,10 +174,6 @@ class Studiensemester extends FHCAPI_Controller
public function getStudySemestersByOrganizationalUnitAndDates($organizationalUnitShortCode)
{
$this->load->model('organisation/Studiengang_model', 'StudiengangModel');
$this->load->model('organisation/Studienordnung_model', 'StudienordnungModel');
$this->load->model('organisation/Studienplan_model', 'StudienplanModel');
$startDate = date('Y-m-d', strtotime($this->input->get('filter[startDate]')));
$endDate = date('Y-m-d', strtotime($this->input->get('filter[endDate]')));
if (!$startDate || !$endDate) {
@@ -34,8 +34,6 @@ class ClassSchedule extends Auth_Controller
);
$this->_setAuthUID();
$this->setControllerId();
}
// -----------------------------------------------------------------------------------------------------------------
+10
View File
@@ -42,3 +42,13 @@
div[role="row"] {
background-color: white;
}
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
+6 -7
View File
@@ -65,14 +65,14 @@ export default {
params,
};
},
editClassTimeSlotsForValidityPeriod(
updateClassTimeSlotsForValidityPeriod(
userId,
classTimeSlotValidityPeriodId,
params,
) {
return {
method: "post",
url: `api/frontend/v1/ClassScheduleApi/editClassTimeSlotsForValidityPeriod/${classTimeSlotValidityPeriodId}`,
url: `api/frontend/v1/ClassScheduleApi/updateClassTimeSlotsForValidityPeriod/${classTimeSlotValidityPeriodId}`,
params,
};
},
@@ -87,14 +87,13 @@ export default {
params,
};
},
deleteClassTimeSlotsForValidityPeriodPerGroup(
deleteClassTimeSlotsForValidityPeriod(
userId,
classTimeSlotValidityPeriodId,
groupIdentifikator,
) {
return {
method: "post",
url: `api/frontend/v1/ClassScheduleApi/deleteClassTimeSlotsForValidityPeriodPerGroup/${classTimeSlotValidityPeriodId}/${groupIdentifikator}`,
url: `api/frontend/v1/ClassScheduleApi/deleteClassTimeSlotsForValidityPeriod/${classTimeSlotValidityPeriodId}`,
};
},
getAllClassScheduleTypes(queryParams) {
@@ -115,14 +114,14 @@ export default {
updateClassTimeSlotType(classTimeSlotTypeId, params) {
return {
method: "post",
url: `api/frontend/v1/ClassScheduleApi/updateClassTimeSlotType/${classTimeSlotTypeId}`,
url: `api/frontend/v1/ClassScheduleApi/updateClassTimeSlotType/${encodeURIComponent(classTimeSlotTypeId)}`,
params,
};
},
deleteClassTimeSlotType(userId, classTimeSlotTypeId) {
return {
method: "post",
url: `api/frontend/v1/ClassScheduleApi/deleteClassTimeSlotType/${classTimeSlotTypeId}`,
url: `api/frontend/v1/ClassScheduleApi/deleteClassTimeSlotType/${encodeURIComponent(classTimeSlotTypeId)}`,
};
},
};
+25
View File
@@ -0,0 +1,25 @@
/**
* 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 {
getAllClassroomHours() {
return {
method: "get",
url: "/api/frontend/v1/ClassroomHourApi/getAllClassroomHours",
};
}
};
@@ -13,6 +13,10 @@ export default {
required: false,
default: false,
},
classroomHours: {
type: [Array, null],
required: true,
},
classTimeSlotTypes: {
type: [Array, null],
required: true,
@@ -22,21 +26,28 @@ export default {
required: false,
default: () => [],
},
defaultClassTimeSlotType: {
type: String,
required: false,
default: null,
},
},
emits: ["overlaysChanged"],
watch: {
editedOverlays: {
async handler(newVal) {
await this.$nextTick();
this.overlays = [];
await this.$nextTick();
this.$refs.calendarContainer
.querySelectorAll("div[id^='overlay-']")
?.querySelectorAll("div[id^='overlay-']")
.forEach((element) => {
element.remove();
});
newVal
await this.$nextTick();
await newVal
.map((slot) => {
return {
...slot,
@@ -48,7 +59,7 @@ export default {
),
};
})
.forEach((overlay) => {
.forEach(async (overlay) => {
let firstElementDataNumber =
(overlay.weekday - 1) * this.timeSlotsInDay.length +
this.timeSlotsInDay.indexOf(overlay.startingTimeSlot);
@@ -64,6 +75,7 @@ export default {
this.$refs.calendarSelectorContainer.querySelector(
"div[data-number='" + lastElementDataNumber + "']",
);
if (!firstSelectedElement || !lastSelectedElement) {
this.$fhcAlert.alertError(
this.$p.t("ui", "classTimeSlotLoadingErrorMessage"),
@@ -84,7 +96,7 @@ export default {
? parseInt(lastSelectedElement.getAttribute("data-number"))
: null;
this.createOverlay();
await this.createOverlay(1);
this.$refs.calendarSelectorContainer
.querySelectorAll("div[class*='part-body']")
@@ -98,15 +110,16 @@ export default {
this.overlays = this.overlays.map((existingOverlay, index) => {
let existingOverlayInNewVal = newVal[index];
let selectedClassTimeSlotType = this.$props.classTimeSlotTypes.find(
(type) =>
type.unterrichtszeitentyp_kurzbz === existingOverlayInNewVal.type,
);
existingOverlay.databaseId = existingOverlayInNewVal.databaseId;
existingOverlay.type = existingOverlayInNewVal.type;
existingOverlay.hexColor =
this.classTimeSlotTypes.find(
(type) =>
type.unterrichtszeitentyp_kurzbz ===
existingOverlayInNewVal.type,
)?.hintergrundfarbe || null;
existingOverlay.backgroundColor =
selectedClassTimeSlotType?.hintergrundfarbe || null;
return {
...existingOverlay,
};
@@ -115,17 +128,6 @@ export default {
deep: true,
immediate: true,
},
classTimeSlotTypes() {
this.overlays = this.overlays.map((overlay) => {
let type = this.classTimeSlotTypes.find(
(type) => type.unterrichtszeitentyp_kurzbz === overlay.type,
);
if (type) {
overlay.hexColor = type.hintergrundfarbe;
}
return overlay;
});
},
overlays: {
handler(newVal) {
this.$emit("overlaysChanged", newVal);
@@ -144,24 +146,6 @@ export default {
this.$p.t("ui", "saturday"),
this.$p.t("ui", "sunday"),
],
timeSlotsInDay: [
"08:00-08:45",
"08:45-09:30",
"09:40-10:25",
"10:25-11:10",
"11:20-12:05",
"12:05-12:50",
"12:50-13:35",
"13:35-14:20",
"14:30-15:15",
"15:15-16:00",
"16:10-16:55",
"16:55-17:40",
"17:50-18:35",
"18:35-19:20",
"19:30-20:15",
"20:15-21:00",
],
isTimeElementCreationInProgress: false,
overlays: [],
defaultTimeSlotLabelColor: "var(--fhc-calendar-bg-body)",
@@ -216,16 +200,30 @@ export default {
userLanguage() {
return Vue.ref(FHC_JS_DATA_STORAGE_OBJECT.user_language);
},
timeSlotsInDay() {
if (!this.$props.classroomHours) return ["08:00-08:45"];
return this.$props.classroomHours;
},
trackOverlayPool() {
let overlaysInPool = this.$refs.overlaysContainer.children;
if (!overlaysInPool < 5) {
alert("Could not get overlays in pool");
return [];
}
},
},
methods: {
createOverlay() {
async createOverlay(test) {
this.hideOverlayClassTimeTypePopover();
let overlayElement;
overlayElement = this.$refs.calendarSelectorContainer.querySelector(
"#overlays-container",
).children[0];
overlayElement = this.$refs.overlaysContainer.children[0];
if (!overlayElement) {
console.error("Could not find overlay element");
return;
}
let firstSelectedChild =
this.$refs.calendarSelectorContainer.querySelector(
@@ -252,7 +250,6 @@ export default {
firstSelectedElementNumber === null ||
lastSelectedElementNumber === null
) {
//console.error("Selected elements do not have data-number attribute");
return;
}
@@ -269,7 +266,7 @@ export default {
this.$fhcAlert.alertError(
this.$p.t("ui", "classTimeSlotMultipleWeeksSelectedErrorMessage"),
);
console.error(error);
return;
}
@@ -347,11 +344,19 @@ export default {
this.timeSlotsInDay[
this.currentLastSelectedElementNumber % this.timeSlotsInDay.length
],
type: null,
hexColor: null,
type:
this.$props.defaultClassTimeSlotType?.unterrichtszeitentyp_kurzbz ||
null,
backgroundColor:
this.$props.defaultClassTimeSlotType?.hintergrundfarbe || null,
weekday: gridLineNumber + 1,
});
}
this.$refs.calendarSelectorContainer
.querySelectorAll("div[class*='part-body']")
.forEach((child) => {
child.style.backgroundColor = this.defaultTimeSlotLabelColor;
});
},
deleteOverlay(overlayId) {
let confirm = window.confirm(
@@ -557,7 +562,7 @@ export default {
});
}
},
handleMouseUp(event) {
async handleMouseUp(event) {
if (this.$props.isPreviewMode) return;
let isLeftMouseButton = event.buttons === 0;
@@ -637,7 +642,7 @@ export default {
});
}
this.createOverlay();
await this.createOverlay(2);
this.$refs.calendarSelectorContainer
.querySelectorAll("div[class*='part-body']")
@@ -725,16 +730,12 @@ export default {
(overlay) => overlay.id === dropzoneItem.id,
);
if (!dropzoneOverlay) {
// console.error(
// "Could not find overlay for dropzone item with id " +
// dropzoneItem.id,
// );
return;
}
if (dropzoneOverlay.id !== this.selected[0].id) {
this.$fhcAlert.alertError(
this.$p.t("ui", "classTimeSlotOverlapErrorMessage"),
this.$p.t("ui", "classTimeSlotOverlapErrorMessage2"),
);
return;
}
@@ -901,7 +902,7 @@ export default {
if (firstSkippedOverElement) {
this.$fhcAlert.alertError(
this.$p.t("ui", "classTimeSlotOverlapErrorMessage"),
this.$p.t("ui", "classTimeSlotOverlapErrorMessage3"),
);
let elementBeforeFirstSkippedOverElement =
this.$refs.calendarSelectorContainer.querySelector(
@@ -1152,7 +1153,7 @@ export default {
if (firstSkippedOverElement) {
this.$fhcAlert.alertError(
this.$p.t("ui", "classTimeSlotOverlapErrorMessage"),
this.$p.t("ui", "classTimeSlotOverlapErrorMessage4"),
);
let elementBeforeFirstSkippedOverElement =
this.$refs.calendarSelectorContainer.querySelector(
@@ -1224,7 +1225,7 @@ export default {
overlayId: null,
};
},
handleMouseOverOnOverlay(overlayId) {
async handleMouseOverOnOverlay(overlayId) {
if (this.$props.isPreviewMode) return;
if (!this.isTimeElementCreationInProgress) return;
@@ -1247,7 +1248,9 @@ export default {
this.currentFirstSelectedElementNumber =
hitOverlayStartingTimeSlotElementNumber + 1;
}
this.createOverlay();
await this.createOverlay(3);
this.isTimeElementCreationInProgress = false;
this.$refs.calendarSelectorContainer
.querySelectorAll("div[class*='part-body']")
@@ -1368,7 +1371,7 @@ export default {
if (!overlay) return "";
let typeDescriptions =
this.classTimeSlotTypes.find(
this.$props.classTimeSlotTypes.find(
(type) => type.unterrichtszeitentyp_kurzbz === overlay.type,
)?.bezeichnung_mehrsprachig || "";
if (!typeDescriptions) return "";
@@ -1378,7 +1381,7 @@ export default {
: typeDescriptions[0].value;
},
handleChangeClassTimeSlotTypeForOverlay(newType) {
let classTimeSlotType = this.classTimeSlotTypes.find(
let classTimeSlotType = this.$props.classTimeSlotTypes.find(
(type) => type.unterrichtszeitentyp_kurzbz === newType,
);
if (!classTimeSlotType) {
@@ -1390,7 +1393,7 @@ export default {
return {
...overlay,
type: classTimeSlotType.unterrichtszeitentyp_kurzbz,
hexColor: classTimeSlotType.hintergrundfarbe,
backgroundColor: classTimeSlotType.hintergrundfarbe,
};
}
return overlay;
@@ -1423,7 +1426,7 @@ export default {
option.addEventListener("click", (event) => {
let selectedTypeDescription = event.currentTarget.innerText;
let selectedType = this.classTimeSlotTypes.find((type) =>
let selectedType = this.$props.classTimeSlotTypes.find((type) =>
type.bezeichnung_mehrsprachig.some(
(desc) => desc.value === selectedTypeDescription,
),
@@ -1437,7 +1440,7 @@ export default {
return {
...overlay,
type: selectedType.unterrichtszeitentyp_kurzbz,
hexColor: selectedType.hintergrundfarbe,
backgroundColor: selectedType.hintergrundfarbe,
};
}
return overlay;
@@ -1466,7 +1469,7 @@ export default {
this.hideOverlayClassTimeTypePopover();
},
template: /*html*/ `
<div ref="calendarSelectorContainer" >
<div ref="calendarSelectorContainer">
<div
ref="calendarContainer"
@mousedown="handleMouseDown"
@@ -1714,9 +1717,9 @@ export default {
</div>
</div>
</div>
<div id="overlays-container" class='d-none'>
<div ref="overlaysContainer" id="overlaysContainer" class='d-none'>
<div
v-for="(index) in 50"
v-for="(index) in 100"
v-draggable:copyLink.noimage="selectedDragObject"
@mousedown='overlaySelectionChanged($event, "overlay-item-" + index)'
@mouseover="handleMouseOverOnOverlay('overlay-item-' + index)"
@@ -1724,7 +1727,9 @@ export default {
:class="{
'fhc-drag-handle': !$props.isPreviewMode,
}"
:style="{ backgroundColor: this.overlays.find(overlay => overlay.id === 'overlay-item-' + index)?.hexColor || this.defaultOverlayColor }"
:style="{
backgroundColor: this.overlays.find(overlay => overlay.id === 'overlay-item-' + index)?.backgroundColor || this.defaultOverlayColor,
}"
:title="getOverlayClassScheduleTypeTitle('overlay-item-' + index)"
class="d-none fhc-pointer-events-all flex-column justify-content-between align-items-center shadow rounded-1"
:draggable='!$props.isPreviewMode ? "true" : "false"'
@@ -1776,7 +1781,7 @@ export default {
</div>
<div ref="classScheduleTypeSelectorContainer" class="d-none">
<div class='d-flex flex-column gap-2'>
<span v-for="(type, index) in classTimeSlotTypes"
<span v-for="(type, index) in $props.classTimeSlotTypes"
:key="index"
:data-type="type.unterrichtszeitentyp_kurzbz"
class="btn btn-sm btn-outline-dark class-schedule-type-selector-option"
@@ -82,11 +82,14 @@ export default {
if (getAllClassTimeSlotTypesResponse.meta.status === "success") {
this.classTimeSlotTypes = getAllClassTimeSlotTypesResponse.data.map(
(type) => {
let descriptions = [];
for (let item of type.bezeichnung_mehrsprachig) {
let [lang, value] = item.split(":");
descriptions.push({ lang, value });
}
let descriptions = [{
lang: "de",
value: type.bezeichnung_mehrsprachig[0] || "",
}, {
lang: "en",
value: type.bezeichnung_mehrsprachig[1] || "",
}];
return {
...type,
bezeichnung_mehrsprachig: descriptions,
@@ -280,7 +280,9 @@ export default {
return events;
},
dropdownParsedOrganizationalUnits() {
return this.organizationalUnits.map((unit) => {
return this.organizationalUnits
.filter((unit) => unit.aktiv)
.map((unit) => {
return {
label: `${unit.bezeichnung} (${unit.organisationseinheittyp_kurzbz})`,
value: unit.oe_kurzbz,
@@ -60,11 +60,14 @@ export default {
if (getAllClassScheduleTypeResponse.meta.status === "success") {
this.classScheduleTypes = getAllClassScheduleTypeResponse.data.map(
(type) => {
let descriptions = [];
for (let item of type.bezeichnung_mehrsprachig) {
let [lang, value] = item.split(":");
descriptions.push({ lang, value });
}
let descriptions = [{
lang: "de",
value: type.bezeichnung_mehrsprachig[0] || "",
}, {
lang: "en",
value: type.bezeichnung_mehrsprachig[1] || "",
}];
return {
...type,
bezeichnung_mehrsprachig: descriptions,
@@ -190,10 +193,10 @@ export default {
<core-form v-else-if="isFormVisible && hasLehreUnterrichtszeitenTypWPermission" ref="classTimeSlotTypeForm" class="row g-3 pb-3">
<div v-if="!isEditInProgress" class="row mb-3">
<form-input
v-model="classTimeSlotTypeFormData.shortCode"
:label="$p.t('ui/shortName')"
type="text"
name="shortCode"
:label="$p.t('ui/shortName')"
v-model="classTimeSlotTypeFormData.shortCode"
>
</form-input>
</div>
@@ -214,20 +217,20 @@ export default {
</div>
<div class="row mb-3">
<form-input
type="text"
name="backgroundColor"
v-model="classTimeSlotTypeFormData.backgroundColor"
:label="$p.t('ui/backgroundColor')"
v-model="classTimeSlotTypeFormData.backgroundColor"
type="color"
name="backgroundColor"
>
</form-input>
</div>
<div class="row mb-3">
<div class="col d-flex align-items-center justify-content-end">
<form-input
v-model="classTimeSlotTypeFormData.isActive"
:label="$p.t('ui/isActive')"
type="checkbox"
name="isActive"
:label="$p.t('ui/isActive')"
v-model="classTimeSlotTypeFormData.isActive"
>
</form-input>
</div>
@@ -1,4 +1,5 @@
import ApiClassSchedule from "../../../js/api/factory/classSchedule.js";
import ApiClassroomHour from "../../../js/api/factory/classroomHour.js";
import ClassScheduleCalendarSelector from "./ClassScheduleCalendarSelector.js";
@@ -8,6 +9,10 @@ export default {
ClassScheduleCalendarSelector,
},
props: {
classTimeSlotTypes: {
type: [Array, null],
required: true,
},
classTimeSlotValidityPeriod: {
type: Object,
required: true,
@@ -17,6 +22,11 @@ export default {
required: false,
default: () => [],
},
classroomHours: {
type: Array,
required: false,
default: () => [],
},
},
emits: ["hideForm", "classTimeSlotsCreated", "classTimeSlotsEdited"],
watch: {
@@ -26,7 +36,7 @@ export default {
return {
databaseId: slot.id,
id: slot.identifier,
weekday: slot.wochentag,
weekday: slot.wochentag === 0 ? 7 : slot.wochentag,
type: slot.unterrichtszeitentyp_kurzbz,
startTime: slot.startTime,
endTime: slot.endTime,
@@ -40,7 +50,6 @@ export default {
return {
editedOverlays: [],
classTimeSlots: [],
classTimeSlotTypes: [],
};
},
computed: {
@@ -78,7 +87,10 @@ export default {
this.$props.classTimeSlotValidityPeriod
.unterrichtszeitengueltigkeit_id,
{
unterrichtszeiten: this.classTimeSlots,
unterrichtszeiten: this.classTimeSlots.map((slot) => {
slot.wochentag = parseInt(slot.wochentag) === 7 ? 0 : slot.wochentag;
return slot;
}),
},
),
);
@@ -87,17 +99,20 @@ export default {
this.classTimeSlots = [];
this.$emit("classTimeSlotsCreated");
} else {
this.$fhcAlert.handleSystemError(error);
this.$fhcAlert.handleSystemError(response.meta.message);
}
},
async editClassTimeSlots() {
async updateClassTimeSlots() {
let response = await this.$api.call(
ApiClassSchedule.editClassTimeSlotsForValidityPeriod(
ApiClassSchedule.updateClassTimeSlotsForValidityPeriod(
this.id,
this.$props.classTimeSlotValidityPeriod
.unterrichtszeitengueltigkeit_id,
{
unterrichtszeiten: this.classTimeSlots,
unterrichtszeiten: this.classTimeSlots.map((slot) => {
slot.wochentag = parseInt(slot.wochentag) === 7 ? 0 : slot.wochentag;
return slot;
}),
},
),
);
@@ -134,35 +149,13 @@ export default {
});
},
},
async created() {
let getAllClassTimeSlotTypesResponse = await this.$api.call(
ApiClassSchedule.getAllClassScheduleTypes("filter[aktiv]=true"),
);
if (getAllClassTimeSlotTypesResponse.meta.status === "success") {
this.classTimeSlotTypes = getAllClassTimeSlotTypesResponse.data.map(
(type) => {
let descriptions = [];
for (let item of type.bezeichnung_mehrsprachig) {
let [lang, value] = item.split(":");
descriptions.push({ lang, value });
}
return {
...type,
bezeichnung_mehrsprachig: descriptions,
};
},
);
} else {
this.$fhcAlert.alertError(
this.$p.t("ui", "errorFetchingClassScheduleTimeSlotTypes"),
);
}
},
template: `
<div class='row'>
<div class='col-12'>
<class-schedule-calendar-selector
:class-time-slot-types="this.classTimeSlotTypes"
<class-schedule-calendar-selector
:classroom-hours="this.$props.classroomHours"
:default-class-time-slot-type="this.$props.classTimeSlotTypes.find(type => type.ist_standard)"
:class-time-slot-types="this.$props.classTimeSlotTypes"
:edited-overlays="this.editedOverlays"
@overlaysChanged="handleOverlaysChanged"
/>
@@ -170,7 +163,7 @@ export default {
<div class="col-12 d-flex justify-content-end gap-2">
<button type="button" class="btn btn-secondary" @click="hideForm">{{$p.t('ui', 'abbrechen')}}</button>
<button v-if="!areClassTimeSlotsEdited" type="button" class="btn btn-primary" @click="createClassTimeSlots">{{$p.t('ui', 'speichern')}}</button>
<button v-else type="button" class="btn btn-primary" @click="editClassTimeSlots">{{$p.t('ui', 'btnAktualisieren')}}</button>
<button v-else type="button" class="btn btn-primary" @click="updateClassTimeSlots">{{$p.t('ui', 'btnAktualisieren')}}</button>
</div>
</div>
`,
@@ -47,19 +47,21 @@ export default {
.call(ApiClassSchedule.getClassTimeValidityPeriod(newValue))
.then((response) => {
let validityPeriodData = response.data[0];
let organizationalUnit = this.organizationalUnits.find(
(unit) => unit.oe_kurzbz === validityPeriodData.oe_kurzbz,
let parsedOrganizationalUnit = this.dropdownParsedOrganizationalUnits.find(
(unit) => unit.value === validityPeriodData.oe_kurzbz,
);
if (!organizationalUnit) {
this.$fhcAlert.alertError(this.$p.t("ui", "errorLoadingData"));
return;
if (!parsedOrganizationalUnit) {
parsedOrganizationalUnit = {
value: validityPeriodData.oe_kurzbz,
label: validityPeriodData.oe_bezeichnung ,
};
}
this.editedClassTimeSlotValidityPeriod = validityPeriodData;
this.classTimeSlotValidityPeriodFormData = {
id: validityPeriodData.unterrichtszeitengueltigkeit_id,
organizationalUnit,
organizationalUnit: parsedOrganizationalUnit,
studyPlanId: validityPeriodData.studienplan_id,
classTimeSlotTypeShortcode:
validityPeriodData.unterrichtszeitentyp_kurzbz,
@@ -136,12 +138,22 @@ export default {
userLanguage() {
return Vue.ref(FHC_JS_DATA_STORAGE_OBJECT.user_language);
},
dropdownParsedOrganizationalUnits() {
return this.organizationalUnits
.filter((unit) => unit.aktiv)
.map((unit) => {
return {
label: `${unit.bezeichnung} (${unit.organisationseinheittyp_kurzbz})`,
value: unit.oe_kurzbz,
};
});
},
},
methods: {
updateClassTimeSlotValidityPeriodFormDataWatcher() {
if (
!this.classTimeSlotValidityPeriodFormData.organizationalUnit
?.oe_kurzbz ||
?.value ||
!this.classTimeSlotValidityPeriodFormData.validityPeriodFrom ||
!this.classTimeSlotValidityPeriodFormData.validityPeriodTo
) {
@@ -153,7 +165,7 @@ export default {
},
async refetchFilterableOptions() {
this.studyPlans = await this.getTargetedStudyPlans(
this.classTimeSlotValidityPeriodFormData.organizationalUnit?.oe_kurzbz,
this.classTimeSlotValidityPeriodFormData.organizationalUnit?.value,
this.formattedValidityPeriodFrom,
this.formattedValidityPeriodTo,
);
@@ -171,11 +183,6 @@ export default {
);
if (editedStudyPlan) {
this.studyPlans.push(editedStudyPlan);
} else {
// console.error(
// "Edited study plan not found:",
// this.editedClassTimeSlotValidityPeriod.studienplan_id,
// );
}
}
}
@@ -183,7 +190,7 @@ export default {
let studySemestersByDates =
await this.getStudySemestersByOrganizationalUnitAndDates(
this.classTimeSlotValidityPeriodFormData.organizationalUnit
?.oe_kurzbz,
?.value,
this.formattedValidityPeriodFrom,
this.formattedValidityPeriodTo,
);
@@ -325,7 +332,7 @@ export default {
...this.classTimeSlotValidityPeriodFormData,
organizationalUnitShortCode:
this.classTimeSlotValidityPeriodFormData.organizationalUnit
?.oe_kurzbz,
?.value,
}),
)
.then((response) => {
@@ -349,7 +356,7 @@ export default {
...this.classTimeSlotValidityPeriodFormData,
organizationalUnitShortCode:
this.classTimeSlotValidityPeriodFormData.organizationalUnit
?.oe_kurzbz,
?.value,
},
),
)
@@ -384,12 +391,12 @@ export default {
const query = event.query.toLowerCase();
if (!query) {
return (this.filteredOrganizationalUnits = [
...this.organizationalUnits,
...this.dropdownParsedOrganizationalUnits,
]);
}
return (this.filteredOrganizationalUnits =
this.organizationalUnits.filter((unit) => {
this.dropdownParsedOrganizationalUnits.filter((unit) => {
let label = `${unit.bezeichnung} (${unit.organisationseinheittyp_kurzbz})`;
return label.toLowerCase().includes(query);
}));
@@ -430,11 +437,14 @@ export default {
if (getAllClassTimeSlotTypesResponse.meta.status === "success") {
this.classTimeSlotTypes = getAllClassTimeSlotTypesResponse.data.map(
(type) => {
let descriptions = [];
for (let item of type.bezeichnung_mehrsprachig) {
let [lang, value] = item.split(":");
descriptions.push({ lang, value });
}
let descriptions = [{
lang: "de",
value: type.bezeichnung_mehrsprachig[0] || "",
}, {
lang: "en",
value: type.bezeichnung_mehrsprachig[1] || "",
}];
return {
...type,
bezeichnung_mehrsprachig: descriptions,
@@ -460,8 +470,8 @@ export default {
v-model="classTimeSlotValidityPeriodFormData.organizationalUnit"
:label="$capitalize($p.t('lehre/organisationseinheit')) + ' *'"
:suggestions="filteredOrganizationalUnits"
:optionValue="(option) => option.kurzbz"
:optionLabel="(option) => option.bezeichnung + ' (' + option.organisationseinheittyp_kurzbz + ')'"
:optionValue="(option) => option.value"
:optionLabel="(option) => option.label"
@complete="filterOrganizationalUnits"
dropdown
forceSelection
@@ -523,7 +533,7 @@ export default {
id="ausbildungssemester"
name="semester"
:disabled="isStudyPlanSelectDisabled"
:label="$p.t('lehre', 'ausbildungssemester')+ '*'"
:label="$p.t('lehre', 'ausbildungssemester')"
v-model="classTimeSlotValidityPeriodFormData.semester"
>
<option
@@ -1,4 +1,5 @@
import ApiClassSchedule from "../../../js/api/factory/classSchedule.js";
import ApiClassroomHour from "../../../js/api/factory/classroomHour.js";
import BsModal from "../Bootstrap/Modal.js";
import ClassScheduleValidityPeriodForm from "./ClassScheduleValidityPeriodForm.js";
@@ -15,9 +16,11 @@ export default {
},
data: () => {
return {
classroomHours: [],
isClassTimeSlotFormVisible: false,
classTimeSlotValidityPeriodId: null,
classTimeSlotValidityPeriod: null,
areClassTimeSlotsLoaded: false,
classTimeSlots: [],
classTimeSlotTypes: [],
editedClassTimeSlots: [],
@@ -32,14 +35,14 @@ export default {
let dateParts = this.classTimeSlotValidityPeriod.gueltig_von
.split("-")
.reverse();
return dateParts.join("/");
return dateParts.join(".");
},
classScheduleValidityPeriodEndDate() {
if (!this.classTimeSlotValidityPeriod) return null;
let dateParts = this.classTimeSlotValidityPeriod.gueltig_bis
.split("-")
.reverse();
return dateParts.join("/");
return dateParts.join(".");
},
},
methods: {
@@ -49,6 +52,7 @@ export default {
this.classTimeSlotValidityPeriodId,
),
);
if (getClassTimeValidityPeriodResponse.meta.status === "success") {
this.classTimeSlotValidityPeriod =
getClassTimeValidityPeriodResponse.data[0];
@@ -74,52 +78,38 @@ export default {
if (
getClassTimeSlotsForValidityPeriodResponse.meta.status === "success"
) {
let classTimeSlotsGroupedByWeek = [];
getClassTimeSlotsForValidityPeriodResponse.data.forEach((slot) => {
let groupIdentifikator = slot["unterrichtszeit_gruppe_identifikator"];
let existingGroup = classTimeSlotsGroupedByWeek.find(
(group) => group.groupIdentifikator === groupIdentifikator,
);
if (existingGroup) {
existingGroup.slots.push(slot);
} else {
classTimeSlotsGroupedByWeek.push({
groupIdentifikator,
slots: [slot],
});
}
});
this.classTimeSlots = classTimeSlotsGroupedByWeek;
this.classTimeSlots = getClassTimeSlotsForValidityPeriodResponse.data;
} else {
this.$fhcAlert.alertError(
this.$p.t("ui", "errorFetchingClassScheduleTimeSlotForValidityPeriod"),
this.$p.t(
"ui",
"errorFetchingClassScheduleTimeSlotForValidityPeriod",
),
);
}
this.areClassTimeSlotsLoaded = true;
},
showClassTimeSlotForm() {
this.isClassTimeSlotFormVisible = true;
},
async editClassTimeSlotsForValidityPeriodPerGroup(groupIdentifikator) {
async editClassTimeSlotsForValidityPeriod() {
await this.fetchClassTimeSlots();
this.editedClassTimeSlots =
this.classTimeSlots
.filter(
(group) => group.groupIdentifikator === groupIdentifikator,
)?.[0]
.slots.map((slot) => {
return {
...slot,
id: slot.unterrichtszeit_id,
startTime: slot.uhrzeit_von,
endTime: slot.uhrzeit_bis,
classTimeSlotTypeShortcode: slot.unterrichtszeitentyp_kurzbz,
};
}) || [];
this.classTimeSlots.map((slot) => {
return {
...slot,
id: slot.unterrichtszeit_id,
startTime: slot.uhrzeit_von,
endTime: slot.uhrzeit_bis,
classTimeSlotTypeShortcode: slot.unterrichtszeitentyp_kurzbz,
};
}) || [];
this.showClassTimeSlotForm();
},
deleteClassTimeSlotsForValidityPeriodPerGroup(groupIdentifikator) {
deleteClassTimeSlotsForValidityPeriod() {
let isDeletionConfirmed = confirm(
this.$p.t("ui", "confirmDeleteClassTimeSlotsForGroup"),
);
@@ -129,10 +119,9 @@ export default {
return this.$api
.call(
ApiClassSchedule.deleteClassTimeSlotsForValidityPeriodPerGroup(
ApiClassSchedule.deleteClassTimeSlotsForValidityPeriod(
this.id,
this.classTimeSlotValidityPeriodId,
groupIdentifikator,
),
)
.then((response) => {
@@ -194,7 +183,7 @@ export default {
this.classTimeSlotValidityPeriodId =
this.$route.params.classTimeSlotValidityPeriodId;
this.fetchClassTimeValidityPeriod();
await this.fetchClassTimeValidityPeriod();
let getAllClassTimeSlotTypesResponse = await this.$api.call(
ApiClassSchedule.getAllClassScheduleTypes("filter[aktiv]=true"),
@@ -202,11 +191,13 @@ export default {
if (getAllClassTimeSlotTypesResponse.meta.status === "success") {
this.classTimeSlotTypes = getAllClassTimeSlotTypesResponse.data.map(
(type) => {
let descriptions = [];
for (let item of type.bezeichnung_mehrsprachig) {
let [lang, value] = item.split(":");
descriptions.push({ lang, value });
}
let descriptions = [{
lang: "de",
value: type.bezeichnung_mehrsprachig[0] || "",
}, {
lang: "en",
value: type.bezeichnung_mehrsprachig[1] || "",
}];
return {
...type,
bezeichnung_mehrsprachig: descriptions,
@@ -219,6 +210,21 @@ export default {
);
}
let getAllClassroomHoursResponse = await this.$api.call(
ApiClassroomHour.getAllClassroomHours(),
);
if (getAllClassroomHoursResponse.meta.status === "success") {
this.classroomHours = getAllClassroomHoursResponse.data.map((hour) => {
return {
...hour,
beginn: hour.beginn.substring(0, 5),
ende: hour.ende.substring(0, 5),
};
});
} else {
this.$fhcAlert.alertError(this.$p.t("ui", "errorFetchingClassroomHours"));
}
this.fetchClassTimeSlots();
},
template: /* html */ `
@@ -250,44 +256,49 @@ export default {
</h5>
</div>
<div>
<div v-if='!isClassTimeSlotFormVisible' class="col-12 d-flex justify-content-end">
<div v-if='!isClassTimeSlotFormVisible && !classTimeSlots.length && areClassTimeSlotsLoaded' class="col-12 d-flex justify-content-end">
<button type="button" class="btn btn-primary" @click="showClassTimeSlotForm">{{$p.t('ui', 'addClassTimeSlotButton')}}</button>
</div>
<class-schedule-validity-period-form
v-else
v-if="isClassTimeSlotFormVisible"
:class-time-slot-types="this.classTimeSlotTypes"
:classroom-hours="this.classroomHours.map(hour => hour.beginn + '-' + hour.ende)"
:class-time-slot-validity-period="classTimeSlotValidityPeriod"
:edited-class-time-slots="editedClassTimeSlots"
@classTimeSlotsCreated="() => { isClassTimeSlotFormVisible = false; fetchClassTimeSlots(); this.editedClassTimeSlots = []; }"
@classTimeSlotsEdited="() => { isClassTimeSlotFormVisible = false; fetchClassTimeSlots(); this.editedClassTimeSlots = []; }"
@hideForm="() => { isClassTimeSlotFormVisible = false; this.editedClassTimeSlots = []; }"
@classTimeSlotsCreated="() => { isClassTimeSlotFormVisible = false; this.areClassTimeSlotsLoaded = false; this.classTimeSlots = []; fetchClassTimeSlots(); this.editedClassTimeSlots = []; }"
@classTimeSlotsEdited="() => { isClassTimeSlotFormVisible = false; this.areClassTimeSlotsLoaded = false; this.classTimeSlots = []; fetchClassTimeSlots(); this.editedClassTimeSlots = []; }"
@hideForm="() => { isClassTimeSlotFormVisible = false; this.areClassTimeSlotsLoaded = false; this.classTimeSlots = []; fetchClassTimeSlots(); this.editedClassTimeSlots = []; }"
class="mb-4"
/>
<div>
<h4>{{ $p.t("ui", "classScheduleValidityPeriodTimeSlots") }}</h4>
</div>
<div v-if="classTimeSlots && Object.keys(classTimeSlots).length > 0">
<div v-for="(classTimeSlotsPerWeek, index) in classTimeSlots" :key="index" class="row border-top rounded p-2 mt-4 mb-2 pt-1 pb-5">
<transition>
<div v-if="classTimeSlots && Object.keys(classTimeSlots).length > 0 && !isClassTimeSlotFormVisible">
<div class="col-12 d-flex align-items-center justify-content-end gap-2">
<a class="ml-auto" @click="editClassTimeSlotsForValidityPeriodPerGroup(classTimeSlotsPerWeek.groupIdentifikator)"><i class="fa fa-edit fs-5"></i></a>
<a class="ml-auto" @click="deleteClassTimeSlotsForValidityPeriodPerGroup(classTimeSlotsPerWeek.groupIdentifikator)"><i class="fa fa-trash text-danger fs-5"></i></a>
<a class="ml-auto" @click="editClassTimeSlotsForValidityPeriod"><i class="fa fa-edit fs-5"></i></a>
<a class="ml-auto" @click="deleteClassTimeSlotsForValidityPeriod"><i class="fa fa-trash text-danger fs-5"></i></a>
</div>
<div class="row border-top rounded p-2 mt-4 mb-2 pt-1 pb-5">
<class-schedule-calendar-selector
:classroom-hours="this.classroomHours.map(hour => hour.beginn + '-' + hour.ende)"
:class-time-slot-types="this.classTimeSlotTypes"
:edited-overlays="classTimeSlots.map((slot) => {
return {
databaseId: slot.id,
id: slot.identifier,
weekday: parseInt(slot.wochentag) === 0 ? 7 : slot.wochentag,
type: slot.unterrichtszeitentyp_kurzbz,
startTime: slot.uhrzeit_von,
endTime: slot.uhrzeit_bis,
};
})"
:isPreviewMode="true"
/>
</div>
<class-schedule-calendar-selector
:class-time-slot-types="this.classTimeSlotTypes"
:edited-overlays="classTimeSlotsPerWeek.slots.map((slot) => {
return {
databaseId: slot.id,
id: slot.identifier,
weekday: slot.wochentag,
type: slot.unterrichtszeitentyp_kurzbz,
startTime: slot.uhrzeit_von,
endTime: slot.uhrzeit_bis,
};
})"
:isPreviewMode="true"
/>
</div>
</div>
<div v-else class="d-flex align-items-center justify-content-center border rounded p-4 mt-4">
</transition>
<div v-if="!classTimeSlots || Object.keys(classTimeSlots).length === 0" class="d-flex align-items-center justify-content-center border rounded p-4 mt-4">
<p class="m-0">{{ $p.t("ui", "noClassScheduleValidityPeriodTimeSlotsFound") }}</p>
</div>
</div>
@@ -1,4 +1,5 @@
import ClassScheduleCalendarSelector from "./ClassScheduleCalendarSelector.js";
import ApiClassroomHour from "../../../js/api/factory/classroomHour.js";
export default {
name: "ClassScheduleValidityPeriodPreview",
@@ -20,7 +21,9 @@ export default {
},
},
data: () => {
return {};
return {
classRoomHours: [],
};
},
computed: {
classScheduleValidityPeriodStartDate() {
@@ -29,38 +32,18 @@ export default {
let dateParts = this.$props.classScheduleValidityPeriod.gueltig_von
.split("-")
.reverse();
return dateParts.join("/");
return dateParts.join(".");
},
classScheduleValidityPeriodEndDate() {
if (!this.$props.classScheduleValidityPeriod) return null;
let dateParts = this.$props.classScheduleValidityPeriod.gueltig_bis
.split("-")
.reverse();
return dateParts.join("/");
return dateParts.join(".");
},
classScheduleValidityPeriodStudyPlan() {
return this.$props.classScheduleValidityPeriod.studienplan_bezeichnung;
},
parsedClassTimeSlots() {
let classTimeSlotsGroupedByWeek = [];
this.$props.classTimeSlots.forEach((slot) => {
let groupIdentifikator = slot["unterrichtszeit_gruppe_identifikator"];
let existingGroup = classTimeSlotsGroupedByWeek.find(
(group) => group.groupIdentifikator === groupIdentifikator,
);
if (existingGroup) {
existingGroup.slots.push(slot);
} else {
classTimeSlotsGroupedByWeek.push({
groupIdentifikator,
slots: [slot],
});
}
});
return classTimeSlotsGroupedByWeek;
},
},
methods: {
showClassScheduleValidityPeriod(classScheduleValidityPeriodId) {
@@ -72,7 +55,22 @@ export default {
});
},
},
async created() {},
async created() {
let getAllClassroomHoursResponse = await this.$api.call(
ApiClassroomHour.getAllClassroomHours(),
);
if (getAllClassroomHoursResponse.meta.status === "success") {
this.classroomHours = getAllClassroomHoursResponse.data.map((hour) => {
return {
...hour,
beginn: hour.beginn.substring(0, 5),
ende: hour.ende.substring(0, 5),
};
});
} else {
this.$fhcAlert.alertError(this.$p.t("ui", "errorFetchingClassroomHours"));
}
},
template: /* html */ `
<div class="container mt-4">
<div class='py-3 d-flex align-items-center justify-content-between'>
@@ -86,15 +84,16 @@ export default {
><i class="fa fa-eye fs-5"></i></a>
</div>
<div class='py-3 mb-4'>
<div v-if="parsedClassTimeSlots && parsedClassTimeSlots.length > 0">
<div v-for="(classTimeSlotsPerWeek, index) in parsedClassTimeSlots" :key="index" class="row border-top rounded pt-1 pb-5">
<div v-if="$props.classTimeSlots && $props.classTimeSlots.length > 0">
<div class="row border-top rounded pt-1 pb-5">
<class-schedule-calendar-selector
:classroom-hours="this.classroomHours.map(hour => hour.beginn + '-' + hour.ende)"
:class-time-slot-types="this.classTimeSlotTypes"
:edited-overlays="classTimeSlotsPerWeek.slots.map((slot) => {
:edited-overlays="$props.classTimeSlots.map((slot) => {
return {
databaseId: slot.id,
id: slot.identifier,
weekday: slot.wochentag,
weekday: parseInt(slot.wochentag) === 0 ? 7 : slot.wochentag,
type: slot.unterrichtszeitentyp_kurzbz,
startTime: slot.uhrzeit_von,
endTime: slot.uhrzeit_bis,
@@ -37,7 +37,6 @@ if(!$result = @$db->db_query("SELECT 1 FROM lehre.tbl_unterrichtszeiten LIMIT 1"
$qry = '
CREATE TABLE lehre.tbl_unterrichtszeiten (
"unterrichtszeit_id" INTEGER NOT NULL,
"unterrichtszeit_gruppe_identifikator" VARCHAR(32) NOT NULL,
"wochentag" INTEGER NOT NULL,
"uhrzeit_von" TIME NOT NULL,
"uhrzeit_bis" TIME NOT NULL,
@@ -104,6 +103,7 @@ if(!$result = @$db->db_query("SELECT 1 FROM lehre.tbl_unterrichtszeiten_typ LIMI
"bezeichnung_mehrsprachig" TEXT[] NOT NULL,
"aktiv" BOOLEAN DEFAULT true,
"hintergrundfarbe" VARCHAR(7),
"ist_standard" BOOLEAN DEFAULT false,
"insertamum" TIMESTAMP WITH TIME ZONE DEFAULT now(),
"insertvon" VARCHAR(32),
"updateamum" TIMESTAMP WITH TIME ZONE DEFAULT now(),
@@ -156,12 +156,12 @@ if(!$result = @$db->db_query("SELECT 1 FROM lehre.tbl_unterrichtszeiten_typ LIMI
$qry = "
INSERT INTO lehre.tbl_unterrichtszeiten_typ (unterrichtszeitentyp_kurzbz, bezeichnung_mehrsprachig, aktiv, hintergrundfarbe) VALUES
('unterrichtszeiten', ARRAY['de:unterrichtszeiten', 'en:teaching times'], 't', '#FFFFFF'),
('vorlesungen', ARRAY['de:Vorlesung', 'en:Lecture'], 't', '#FF8A8A'),
('backuptage', ARRAY['de:Übung', 'en:Exercise'], 't', '#8AFF8A'),
('ausgleichswochen', ARRAY['de:Ausgleichswochen', 'en:Compensation Weeks'], 't', '#8A8AFF'),
('prüfungswochen', ARRAY['de:Prüfungswochen', 'en:Exam Weeks'], 't', '#FFFF8A');
INSERT INTO lehre.tbl_unterrichtszeiten_typ (unterrichtszeitentyp_kurzbz, bezeichnung_mehrsprachig, aktiv, hintergrundfarbe, ist_standard) VALUES
('unterrichtszeiten', ARRAY['Unterrichtszeiten', 'Teaching Times'], 't', '#FFFFFF', 'f'),
('vorlesungen', ARRAY['Vorlesung', 'Lecture'], 't', '#FF8A8A', 't'),
('backuptage', ARRAY['Übung', 'Exercise'], 't', '#8AFF8A', 'f'),
('ausgleichswochen', ARRAY['Ausgleichswochen', 'Compensation Weeks'], 't', '#8A8AFF', 'f'),
('prüfungswochen', ARRAY['Prüfungswochen', 'Exam Weeks'], 't', '#FFFF8A', 'f');
";
if(!$db->db_query($qry))
+22 -2
View File
@@ -57844,13 +57844,13 @@ I have been informed that I am under no obligation to consent to the transmissio
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Kein Gültigkeitszeitraum für diese Unterrichtszeit gefunden',
'text' => 'Kein Gültigkeitszeitraum gefunden',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'No validity period found for this class time slot',
'text' => 'No validity period found',
'description' => '',
'insertvon' => 'system'
),
@@ -58636,6 +58636,26 @@ I have been informed that I am under no obligation to consent to the transmissio
)
)
),
array(
'app' => 'core',
'category' => 'ui',
'phrase' => 'errorFetchingClassroomHours',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Fehler beim Abrufen der Unterrichtsstunden',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Error fetching classroom hours',
'description' => '',
'insertvon' => 'system'
)
)
),
);