diff --git a/application/config/routes.php b/application/config/routes.php
index aa4ba9db8..0df4a5f00 100644
--- a/application/config/routes.php
+++ b/application/config/routes.php
@@ -119,6 +119,9 @@ $route['api/frontend/v1/stv/[sS]tudents/([WS]S[0-9]{4})/prestudent/(:num)'] = 'a
// // (studiensemester_kurzbz)/person/(person_id)
$route['api/frontend/v1/stv/[sS]tudents/([WS]S[0-9]{4})/person/(:num)'] = 'api/frontend/v1/stv/Students/getPerson/$1/$2';
+
+$route['lehre/ClassSchedule/(:any)/(:any)'] = 'lehre/ClassSchedule/index';
+
// load routes from extensions, also look for environment-specific configs
$subdirs = ['application/config/extensions', 'application/config/' . ENVIRONMENT . '/extensions'];
diff --git a/application/controllers/api/frontend/v1/ClassScheduleApi.php b/application/controllers/api/frontend/v1/ClassScheduleApi.php
new file mode 100644
index 000000000..564e395c5
--- /dev/null
+++ b/application/controllers/api/frontend/v1/ClassScheduleApi.php
@@ -0,0 +1,477 @@
+.
+ */
+
+if (! defined('BASEPATH')) exit('No direct script access allowed');
+class ClassScheduleApi extends FHCAPI_Controller
+{
+
+ /**
+ * Object initialization
+ */
+ public function __construct()
+ {
+ parent::__construct([
+ 'getAllClassTimeValidityPeriods'=> array('lehre/unterrichtszeiten_gk:r'),
+ 'getClassTimeValidityPeriod' => array('lehre/unterrichtszeiten_gk:r'),
+ 'createClassTimeSlotValidityPeriod' => array('lehre/unterrichtszeiten_gk:rw'),
+ 'updateClassTimeSlotValidityPeriod' => array('lehre/unterrichtszeiten_gk:rw'),
+ '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'),
+ 'getAllClassScheduleTypes' => array('lehre/unterrichtszeiten_typ:r'),
+ 'createClassTimeSlotType' => array('lehre/unterrichtszeiten_typ:rw'),
+ 'updateClassTimeSlotType' => array('lehre/unterrichtszeiten_typ:rw'),
+ 'deleteClassTimeSlotType' => array('lehre/unterrichtszeiten_typ:rw'),
+ ]);
+
+ $this->load->library('form_validation');
+
+ $this->load->model('education/ClassTimeSlotValidityPeriod_model', "ClassTimeSlotValidityPeriodModel");
+ $this->load->model('education/ClassTimeSlot_model', "ClassTimeSlotModel");
+ $this->load->model('education/ClassTimeSlotType_model', "ClassTimeSlotTypeModel");
+
+ // Loads phrases system
+ $this->loadPhrases([
+ 'global'
+ ]);
+
+ }
+
+ //------------------------------------------------------------------------------------------------------------------
+ // Public methods
+
+ public function getAllClassTimeValidityPeriods()
+ {
+ $this->ClassTimeSlotValidityPeriodModel->addJoin('lehre.tbl_studienplan', 'lehre.tbl_studienplan.studienplan_id=lehre.tbl_unterrichtszeiten_gueltigkeit.studienplan_id', 'LEFT');
+ $this->ClassTimeSlotValidityPeriodModel->addOrder('gueltig_von', 'DESC');
+ $class_time_slot_validity_period_res = $this->ClassTimeSlotValidityPeriodModel->load();
+ $class_time_slot_validity_period_res = $this->getDataOrTerminateWithError($class_time_slot_validity_period_res);
+ $this->terminateWithSuccess($class_time_slot_validity_period_res);
+ }
+
+ public function getClassTimeValidityPeriod($classTimeSlotValidityPeriodId)
+ {
+ $this->ClassTimeSlotValidityPeriodModel->addJoin('lehre.tbl_studienplan', 'lehre.tbl_studienplan.studienplan_id=lehre.tbl_unterrichtszeiten_gueltigkeit.studienplan_id', 'LEFT');
+ $class_time_slot_validity_period_res = $this->ClassTimeSlotValidityPeriodModel->load($classTimeSlotValidityPeriodId);
+ $class_time_slot_validity_period_res = $this->getDataOrTerminateWithError($class_time_slot_validity_period_res);
+ $this->terminateWithSuccess($class_time_slot_validity_period_res);
+ }
+
+ public function createClassTimeSlotValidityPeriod()
+ {
+ $this->form_validation->set_rules('validityPeriodFrom', 'Validity Period From', 'required|is_valid_date[Y-m-d]');
+ $this->form_validation->set_rules('validityPeriodTo', 'Validity Period To', 'required|is_valid_date[Y-m-d]|callback_date_greater_equal[validityPeriodFrom]');
+ $this->form_validation->set_rules('degreeProgramShortcode', 'Degree Program Shortcode', 'required|max_length[32]');
+ $this->form_validation->set_rules('semester', 'Semester', 'required|is_natural_no_zero|less_than_equal_to[8]');
+ $this->form_validation->set_rules('classTimeSlotTypeShortcode', 'Class Time Slot Type Shortcode', 'max_length[32]');
+ $this->form_validation->set_rules('studyPlanId', 'Study Plan ID', 'is_natural_no_zero');
+
+ if($this->form_validation->run() == FALSE) $this->terminateWithValidationErrors($this->form_validation->error_array());
+
+ $this->db->trans_start();
+
+ $result = $this->ClassTimeSlotValidityPeriodModel->insert([
+ 'gueltig_von' => $this->input->post('validityPeriodFrom'),
+ 'gueltig_bis' => $this->input->post('validityPeriodTo'),
+ 'oe_kurzbz' => $this->input->post('degreeProgramShortcode'),
+ 'ausbildungssemester' => $this->input->post('semester'),
+ 'anmerkung' => $this->input->post('description'),
+ 'unterrichtszeitentyp_kurzbz' => $this->input->post('classTimeSlotTypeShortcode') ?? '',
+ 'studienplan_id' => $this->input->post('studyPlanId'),
+ 'insertamum' => date('c'),
+ 'insertvon' => getAuthUid(),
+ 'updateamum' => date('c'),
+ 'updatevon' => getAuthUid(),
+ ]);
+
+ $data = $this->getDataOrTerminateWithError($result);
+
+ $this->db->trans_complete();
+
+ $this->terminateWithSuccess(true);
+
+ $this->terminateWithSuccess($this->input->post());
+ }
+
+ public function updateClassTimeSlotValidityPeriod($classTimeSlotValidityPeriodId)
+ {
+ $this->form_validation->set_rules('validityPeriodFrom', 'Validity Period From', 'required|is_valid_date[Y-m-d]');
+ $this->form_validation->set_rules('validityPeriodTo', 'Validity Period To', 'required|is_valid_date[Y-m-d]|callback_date_greater_equal[validityPeriodFrom]');
+ $this->form_validation->set_rules('degreeProgramShortcode', 'Degree Program Shortcode', 'required|max_length[32]');
+ $this->form_validation->set_rules('semester', 'Semester', 'required|is_natural_no_zero|less_than_equal_to[8]');
+ $this->form_validation->set_rules('classTimeSlotTypeShortcode', 'Class Time Slot Type Shortcode', 'max_length[32]');
+ $this->form_validation->set_rules('studyPlanId', 'Study Plan ID', 'is_natural_no_zero');
+
+ if($this->form_validation->run() == FALSE) $this->terminateWithValidationErrors($this->form_validation->error_array());
+
+ $result = $this->ClassTimeSlotValidityPeriodModel->update($classTimeSlotValidityPeriodId, [
+ 'gueltig_von' => $this->input->post('validityPeriodFrom'),
+ 'gueltig_bis' => $this->input->post('validityPeriodTo'),
+ 'oe_kurzbz' => $this->input->post('degreeProgramShortcode'),
+ 'ausbildungssemester' => $this->input->post('semester'),
+ 'anmerkung' => $this->input->post('description'),
+ 'unterrichtszeitentyp_kurzbz' => $this->input->post('classTimeSlotTypeShortcode'),
+ 'studienplan_id' => $this->input->post('studyPlanId'),
+ 'updateamum' => date('c'),
+ 'updatevon' => getAuthUid(),
+ ]);
+
+ $data = $this->getDataOrTerminateWithError($result);
+
+ $this->terminateWithSuccess(true);
+ }
+
+ public function deleteClassTimeSlotValidityPeriod($classTimeSlotValidityPeriodId)
+ {
+ $result = $this->ClassTimeSlotValidityPeriodModel->delete($classTimeSlotValidityPeriodId);
+ if (isError($result)) {
+ $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
+ }
+
+ $result = $this->ClassTimeSlotModel->delete(['unterrichtszeitengueltigkeit_id'=> $classTimeSlotValidityPeriodId]);
+ if (isError($result)) {
+ $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
+ }
+
+ $this->terminateWithSuccess(true);
+ }
+
+ public function getClassTimeSlotsForValidityPeriod($classTimeSlotValidityPeriodId)
+ {
+ $this->ClassTimeSlotModel->addOrder('insertamum', 'DESC');
+ $class_time_slots_res = $this->ClassTimeSlotModel->loadWhere(['unterrichtszeitengueltigkeit_id' => $classTimeSlotValidityPeriodId]);
+ $class_time_slots_res = $this->getDataOrTerminateWithError($class_time_slots_res);
+ $this->terminateWithSuccess($class_time_slots_res);
+ }
+
+ public function createClassTimeSlotsForValidityPeriod($classTimeSlotValidityPeriodId)
+ {
+ $this->form_validation->set_rules('classTimeSlots', 'Validity Period From', 'callback_validate_items_in_class_time_slots');
+ if($this->form_validation->run() == FALSE) $this->terminateWithValidationErrors($this->form_validation->error_array());
+
+ $this->db->trans_start();
+
+ $timeSlotGroupIdentifier = uniqid();
+
+ foreach ($this->input->post('classTimeSlots') as $timeSlot) {
+ $result = $this->ClassTimeSlotModel->insert([
+ 'unterrichtszeit_gruppe_identifikator' => $timeSlotGroupIdentifier,
+ 'wochentag' => $timeSlot['wochentag'],
+ 'uhrzeit_von' => $timeSlot['startTime'],
+ 'uhrzeit_bis' => $timeSlot['endTime'],
+ 'unterrichtszeitentyp_kurzbz' => $timeSlot['classTimeSlotTypeShortcode'],
+ 'unterrichtszeitengueltigkeit_id' => $classTimeSlotValidityPeriodId,
+ 'insertamum' => date('c'),
+ 'insertvon' => getAuthUid(),
+ 'updateamum' => date('c'),
+ 'updatevon' => getAuthUid(),
+ ]);
+
+ $this->getDataOrTerminateWithError($result);
+ }
+ $this->db->trans_complete();
+
+ $this->terminateWithSuccess(true);
+ }
+
+ public function editClassTimeSlotsForValidityPeriod($classTimeSlotValidityPeriodId)
+ {
+ $this->form_validation->set_rules('classTimeSlots', 'Validity Period From', 'callback_validate_items_in_class_time_slots');
+ if($this->form_validation->run() == FALSE) $this->terminateWithValidationErrors($this->form_validation->error_array());
+
+ $this->db->trans_start();
+
+ $timeSlotGroupIdentifier = uniqid();
+
+ foreach ($this->input->post('classTimeSlots') as $timeSlot) {
+ $data = [
+ 'unterrichtszeit_gruppe_identifikator' => $timeSlotGroupIdentifier,
+ 'wochentag' => $timeSlot['wochentag'],
+ 'uhrzeit_von' => $timeSlot['startTime'],
+ 'uhrzeit_bis' => $timeSlot['endTime'],
+ 'unterrichtszeitentyp_kurzbz' => $timeSlot['classTimeSlotTypeShortcode'],
+ 'unterrichtszeitengueltigkeit_id' => $classTimeSlotValidityPeriodId,
+ 'updateamum' => date('c'),
+ 'updatevon' => getAuthUid(),
+ ];
+ if (isset($timeSlot['id'])) {
+ $result = $this->ClassTimeSlotModel->update($timeSlot['id'], $data);
+ } else {
+ $result = $this->ClassTimeSlotModel->insert(array_merge($data, [
+ 'insertvon' => getAuthUid(),
+ 'updateamum' => date('c'),
+ ]));
+ }
+
+ $this->getDataOrTerminateWithError($result);
+ }
+ $this->db->trans_complete();
+
+ $this->terminateWithSuccess(true);
+
+ }
+ public function deleteClassTimeSlotsForValidityPeriodPerGroup($classTimeSlotValidityPeriodId, $groupIdentifikator)
+ {
+ $result = $this->ClassTimeSlotModel->delete(['unterrichtszeitengueltigkeit_id'=> $classTimeSlotValidityPeriodId, 'unterrichtszeit_gruppe_identifikator' => $groupIdentifikator]);
+ if (isError($result)) {
+ $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
+ }
+
+ $this->terminateWithSuccess(true);
+ }
+
+ public function getAllClassScheduleTypes()
+ {
+ $filter = $this->input->get('filter');
+
+ if ($filter) {
+ if (isset($filter['aktiv'])) {
+ $this->ClassTimeSlotTypeModel->db->where('aktiv', $filter['aktiv']);
+ }
+ }
+
+ $class_schedule_types_res = $this->ClassTimeSlotTypeModel->load();
+ $class_schedule_types_res = $this->getDataOrTerminateWithError($class_schedule_types_res);
+
+ $this->terminateWithSuccess($class_schedule_types_res);
+ }
+
+ public function createClassTimeSlotType()
+ {
+ $this->form_validation->set_rules('shortCode', 'Short Code', 'required|max_length[32]');
+ $this->form_validation->set_rules('descriptions', 'Descriptions', 'callback_validate_descriptions_array');
+
+ if($this->form_validation->run() == FALSE) $this->terminateWithValidationErrors($this->form_validation->error_array());
+
+ $this->db->trans_start();
+
+ $descriptions = $this->input->post('descriptions');
+ $pgArray = $this->arrayToPgArray($descriptions);
+
+ $query = 'INSERT INTO lehre.tbl_unterrichtszeiten_typ (
+ unterrichtszeitentyp_kurzbz,
+ bezeichnung_mehrsprachig,
+ aktiv,
+ insertamum,
+ insertvon,
+ updateamum,
+ updatevon) VALUES (?, ?, ?, ?, ?, ?, ?)';
+ $result = $this->db->query($query, [
+ $this->input->post('shortCode'),
+ $pgArray,
+ $this->input->post('isActive'),
+ date('c'),
+ getAuthUid(),
+ date('c'),
+ getAuthUid(),
+ ]);
+ $this->db->trans_complete();
+
+ $this->terminateWithSuccess(true);
+ }
+
+ public function updateClassTimeSlotType($classTimeSlotTypeId)
+ {
+ $this->form_validation->set_rules('descriptions', 'Descriptions', 'callback_validate_descriptions_array');
+
+ if($this->form_validation->run() == FALSE) $this->terminateWithValidationErrors($this->form_validation->error_array());
+
+ $this->db->trans_start();
+
+ $descriptions = $this->input->post('descriptions');
+ $pgArray = $this->arrayToPgArray($descriptions);
+
+ $query = 'UPDATE lehre.tbl_unterrichtszeiten_typ SET bezeichnung_mehrsprachig = ?, aktiv = ?, updateamum = ?, updatevon = ? WHERE unterrichtszeitentyp_kurzbz = ?';
+ $result = $this->db->query($query, [
+ $pgArray,
+ $this->input->post('isActive'),
+ date('c'),
+ getAuthUid(),
+ $classTimeSlotTypeId,
+ ]);
+ $this->db->trans_complete();
+
+ $this->terminateWithSuccess(true);
+ }
+
+ public function deleteClassTimeSlotType($classTimeSlotTypeId)
+ {
+ $result = $this->ClassTimeSlotTypeModel->delete(['unterrichtszeitentyp_kurzbz' => $classTimeSlotTypeId]);
+ if (isError($result)) {
+ $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
+ }
+
+ $this->terminateWithSuccess(true);
+ }
+ //------------------------------------------------------------------------------------------------------------------
+ // Private methods
+ private function arrayToPgArray(array $assoc) {
+ $flat = [];
+
+ foreach ($assoc as $assocItem) {
+ $flat[] = $assocItem['lang'] . ':' . $assocItem['value'];
+ }
+
+ $escaped = array_map(function ($v) {
+ return '"' . addslashes($v) . '"';
+ }, $flat);
+ return '{' . implode(',', $escaped) . '}';
+ }
+
+ public function date_greater_equal($toDate, $fromField)
+ {
+ $fromDate = $this->input->post($fromField);
+
+ // Check if "from" exists
+ if (!$fromDate) {
+ $this->form_validation->set_message(
+ 'date_greater_equal',
+ 'Validity Period From is required.'
+ );
+ return false;
+ }
+
+ // Validate both dates
+ if (!strtotime($toDate) || !strtotime($fromDate)) {
+ $this->form_validation->set_message(
+ 'date_greater_equal',
+ 'Both dates must be valid.'
+ );
+ return false;
+ }
+
+ // Compare dates
+ if (strtotime($toDate) < strtotime($fromDate)) {
+ $this->form_validation->set_message(
+ 'date_greater_equal',
+ 'The {field} must be greater than or equal to Validity Period From.'
+ );
+ return false;
+ }
+
+ return true;
+ }
+
+ public function validate_items_in_class_time_slots($classTimeSlots)
+ {
+ // see if $classTimeSlots is an array and has at least one item
+
+ if (!is_array($this->input->post('classTimeSlots')) || count($this->input->post('classTimeSlots')) === 0) {
+ $this->form_validation->set_message(
+ 'validate_items_in_class_time_slots',
+ 'At least one class time slot is required.'
+ );
+ return false;
+ }
+
+ foreach ($this->input->post('classTimeSlots') as $index => $timeSlot) {
+ if (!isset($timeSlot['wochentag'], $timeSlot['startTime'], $timeSlot['endTime'], $timeSlot['classTimeSlotTypeShortcode'])) {
+ $this->form_validation->set_message(
+ 'validate_items_in_class_time_slots',
+ 'Each class time slot must have a weekday, start time, end time and class time slot type shortcode.'
+ );
+ return false;
+ }
+
+ if (!in_array($timeSlot['wochentag'], [1, 2, 3, 4, 5])) {
+ $this->form_validation->set_message(
+ 'validate_items_in_class_time_slots',
+ 'Weekday must be an integer between 1 (Monday) and 5 (Friday).'
+ );
+ return false;
+ }
+
+
+ log_message('error', 'Validating class time slots: ' . print_r($timeSlot['startTime'], true));
+ if (!strtotime($timeSlot['startTime']) || !strtotime($timeSlot['endTime'])) {
+ $this->form_validation->set_message(
+ 'validate_items_in_class_time_slots',
+ 'Start time and end time must be valid time strings.'
+ );
+ return false;
+ }
+
+ if (strtotime($timeSlot['endTime']) <= strtotime($timeSlot['startTime'])) {
+ $this->form_validation->set_message(
+ 'validate_items_in_class_time_slots',
+ 'End time must be greater than start time.'
+ );
+ return false;
+ }
+ }
+
+ $slotsByDay = [];
+ foreach ($this->input->post('classTimeSlots') as $timeSlot) {
+ $slotsByDay[$timeSlot['wochentag']][] = $timeSlot;
+ }
+
+ foreach ($slotsByDay as $day => $slots) {
+ usort($slots, function ($a, $b) {
+ return strtotime($a['startTime']) - strtotime($b['startTime']);
+ });
+
+ for ($i = 1; $i < count($slots); $i++) {
+ if (strtotime($slots[$i]['startTime']) < strtotime($slots[$i - 1]['endTime'])) {
+ $this->form_validation->set_message(
+ 'validate_items_in_class_time_slots',
+ 'Class time slots for each day must not overlap.'
+ );
+ return false;
+ }
+ }
+ }
+
+
+ return true;
+ }
+
+ public function validate_descriptions_array($descriptions)
+ {
+ $descriptions = $this->input->post('descriptions');
+ if (!is_array($descriptions) || count($descriptions) === 0) {
+ $this->form_validation->set_message(
+ 'validate_descriptions_array',
+ 'Descriptions must be a non-empty array.'
+ );
+ return false;
+ }
+
+ foreach ($descriptions as $index => $description) {
+ if (!isset($description['lang'], $description['value'])) {
+ $this->form_validation->set_message(
+ 'validate_descriptions_array',
+ 'Each description must have a language and a value.'
+ );
+ return false;
+ }
+
+ if (empty($description['lang']) || empty($description['value'])) {
+ $this->form_validation->set_message(
+ 'validate_descriptions_array',
+ 'Language and value in each description must not be empty.'
+ );
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
+
diff --git a/application/controllers/lehre/ClassSchedule.php b/application/controllers/lehre/ClassSchedule.php
new file mode 100644
index 000000000..d96551bf5
--- /dev/null
+++ b/application/controllers/lehre/ClassSchedule.php
@@ -0,0 +1,64 @@
+ array('lehre/unterrichtszeiten_gk:r'),
+ )
+ );
+
+ $this->load->library('PermissionLib');
+ $this->load->library('AuthLib');
+
+ $this->loadPhrases(
+ array(
+ 'ui',
+ 'global',
+ 'person',
+ 'abschlusspruefung',
+ 'password',
+ 'lehre'
+ )
+ );
+
+ $this->_setAuthUID();
+
+ $this->setControllerId();
+ }
+
+ // -----------------------------------------------------------------------------------------------------------------
+ // Public methods
+ public function index()
+ {
+ return $this->load->view('lehre/class_schedule/index.php', [
+ 'permissions' => [
+ 'lehre/unterrichtszeiten_typ_w' => $this->permissionlib->isBerechtigt('lehre/unterrichtszeiten_typ', 'suid'),
+ ],
+ ]);
+ }
+
+ // -----------------------------------------------------------------------------------------------------------------
+ // Private methods
+
+ /**
+ * Retrieve the UID of the logged user and checks if it is valid
+ */
+ private function _setAuthUID()
+ {
+ $this->_uid = getAuthUID();
+
+ if (!$this->_uid) show_error('User authentification failed');
+ }
+}
diff --git a/application/views/lehre/class_schedule/index.php b/application/views/lehre/class_schedule/index.php
new file mode 100644
index 000000000..3cd56b2cd
--- /dev/null
+++ b/application/views/lehre/class_schedule/index.php
@@ -0,0 +1,40 @@
+ 'Unterrichtszeiten der Studiengänge',
+ 'vue3' => true,
+ 'axios027' => true,
+ 'bootstrap5' => true,
+ 'tabulator5' => true,
+ 'fontawesome6' => true,
+ 'primevue3' => true,
+ 'navigationcomponent' => true,
+ 'filtercomponent' => true,
+ 'customJSs' => array(
+ 'vendor/vuejs/vuedatepicker_js/vue-datepicker.iife.js',
+ 'vendor/moment/luxonjs/luxon.min.js'
+ ),
+ 'customJSModules' => array(
+ 'public/js/apps/lehre/ClassScheduleApp.js'
+ ),
+ 'customCSSs' => array(
+ 'public/css/components/vue-datepicker.css',
+ 'public/css/components/primevue.css',
+ 'public/css/components/verticalsplit.css',
+ 'public/extensions/FHC-Core-Developer/css/FhcMain.css'
+ )
+);
+
+$this->load->view('templates/FHC-Header', $includesArray);
+?>
+
+
+
+
+
+
+
+load->view('templates/FHC-Footer', $includesArray); ?>
+
diff --git a/public/js/api/factory/classSchedule.js b/public/js/api/factory/classSchedule.js
new file mode 100644
index 000000000..431b7ce8f
--- /dev/null
+++ b/public/js/api/factory/classSchedule.js
@@ -0,0 +1,121 @@
+/**
+ * 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 .
+ */
+
+export default {
+ getAllClassTimeValidityPeriods() {
+ return {
+ method: "get",
+ url: "/api/frontend/v1/ClassScheduleApi/getAllClassTimeValidityPeriods",
+ };
+ },
+ getClassTimeValidityPeriod(classTimeSlotValidityPeriodId) {
+ return {
+ method: "get",
+ url: `/api/frontend/v1/ClassScheduleApi/getClassTimeValidityPeriod/${classTimeSlotValidityPeriodId}`,
+ };
+ },
+ createClassTimeSlotValidityPeriod(userId, params) {
+ return {
+ method: "post",
+ url: "api/frontend/v1/ClassScheduleApi/createClassTimeSlotValidityPeriod/",
+ params,
+ };
+ },
+ deleteClassTimeSlotValidityPeriod(userId, classTimeSlotValidityPeriodId) {
+ return {
+ method: "post",
+ url: `api/frontend/v1/ClassScheduleApi/deleteClassTimeSlotValidityPeriod/${classTimeSlotValidityPeriodId}`,
+ };
+ },
+ getClassTimeSlotsForValidityPeriod(classTimeSlotValidityPeriodId) {
+ return {
+ method: "get",
+ url: `api/frontend/v1/ClassScheduleApi/getClassTimeSlotsForValidityPeriod/${classTimeSlotValidityPeriodId}`,
+ };
+ },
+ createClassTimeSlotsForValidityPeriod(
+ userId,
+ classTimeSlotValidityPeriodId,
+ params,
+ ) {
+ return {
+ method: "post",
+ url: `api/frontend/v1/ClassScheduleApi/createClassTimeSlotsForValidityPeriod/${classTimeSlotValidityPeriodId}`,
+ params,
+ };
+ },
+ editClassTimeSlotsForValidityPeriod(
+ userId,
+ classTimeSlotValidityPeriodId,
+ params,
+ ) {
+ return {
+ method: "post",
+ url: `api/frontend/v1/ClassScheduleApi/editClassTimeSlotsForValidityPeriod/${classTimeSlotValidityPeriodId}`,
+ params,
+ };
+ },
+ updateClassTimeSlotValidityPeriod(
+ userId,
+ classTimeSlotValidityPeriodId,
+ params,
+ ) {
+ return {
+ method: "post",
+ url: `api/frontend/v1/ClassScheduleApi/updateClassTimeSlotValidityPeriod/${classTimeSlotValidityPeriodId}`,
+ params,
+ };
+ },
+ deleteClassTimeSlotsForValidityPeriodPerGroup(
+ userId,
+ classTimeSlotValidityPeriodId,
+ groupIdentifikator,
+ ) {
+ return {
+ method: "post",
+ url: `api/frontend/v1/ClassScheduleApi/deleteClassTimeSlotsForValidityPeriodPerGroup/${classTimeSlotValidityPeriodId}/${groupIdentifikator}`,
+ };
+ },
+ getAllClassScheduleTypes(queryParams) {
+ return {
+ method: "get",
+ url:
+ "/api/frontend/v1/ClassScheduleApi/getAllClassScheduleTypes?" +
+ new URLSearchParams(queryParams).toString(),
+ };
+ },
+ createClassTimeSlotType(params) {
+ return {
+ method: "post",
+ url: "api/frontend/v1/ClassScheduleApi/createClassTimeSlotType/",
+ params,
+ };
+ },
+ updateClassTimeSlotType(classTimeSlotTypeId, params) {
+ return {
+ method: "post",
+ url: `api/frontend/v1/ClassScheduleApi/updateClassTimeSlotType/${classTimeSlotTypeId}`,
+ params,
+ };
+ },
+ deleteClassTimeSlotType(userId, classTimeSlotTypeId) {
+ return {
+ method: "post",
+ url: `api/frontend/v1/ClassScheduleApi/deleteClassTimeSlotType/${classTimeSlotTypeId}`,
+ };
+ },
+};
diff --git a/public/js/apps/lehre/ClassScheduleApp.js b/public/js/apps/lehre/ClassScheduleApp.js
new file mode 100644
index 000000000..c5ff33274
--- /dev/null
+++ b/public/js/apps/lehre/ClassScheduleApp.js
@@ -0,0 +1,59 @@
+/**
+ * Copyright (C) 2023 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 .
+ */
+
+import ClassScheduleOverview from "../../components/ClassSchedule/ClassScheduleOverview.js";
+import ClassScheduleValidityPeriodOverview from "../../components/ClassSchedule/ClassScheduleValidityPeriodOverview.js";
+
+import FhcAlert from "../../plugins/FhcAlert.js";
+import Phrasen from "../../plugins/Phrasen.js";
+import FhcApi from "../../plugins/Api.js";
+
+const ciPath =
+ FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, "") +
+ FHC_JS_DATA_STORAGE_OBJECT.ci_router;
+
+const router = VueRouter.createRouter({
+ history: VueRouter.createWebHistory(),
+ routes: [
+ {
+ name: "overview",
+ path: `/${ciPath}/lehre/ClassSchedule`,
+ component: ClassScheduleOverview,
+ },
+ {
+ name: "validityPeriodOverview",
+ path: `/${ciPath}/lehre/ClassSchedule/validityPeriods/:classTimeSlotValidityPeriodId`,
+ component: ClassScheduleValidityPeriodOverview,
+ params: true,
+ },
+ ],
+});
+
+const app = Vue.createApp({
+ components: {
+ ClassScheduleOverview,
+ ClassScheduleValidityPeriodOverview,
+ },
+});
+
+app
+ .use(router)
+ .use(primevue.config.default, { zIndex: { overlay: 9999 } })
+ .use(FhcAlert)
+ .use(Phrasen)
+ .use(FhcApi)
+ .mount("#main");
diff --git a/public/js/components/ClassSchedule/ClassScheduleOverview.js b/public/js/components/ClassSchedule/ClassScheduleOverview.js
new file mode 100644
index 000000000..e27a63932
--- /dev/null
+++ b/public/js/components/ClassSchedule/ClassScheduleOverview.js
@@ -0,0 +1,276 @@
+// Import the Core Filter- and Core RESTClient Component to build your table and handle data
+import { CoreFilterCmpt } from "../filter/Filter.js";
+import ApiClassSchedule from "../../../js/api/factory/classSchedule.js";
+
+import ClassScheduleTypeModal from "./ClassScheduleTypeModal.js";
+import ClassScheduleValidityPeriodModal from "./ClassScheduleValidityPeriodModal.js";
+
+export default {
+ name: "ClassScheduleOverview",
+ components: {
+ CoreFilterCmpt,
+ ClassScheduleTypeModal,
+ ClassScheduleValidityPeriodModal,
+ },
+ props: {
+ permissions: Object,
+ },
+ provide() {
+ return {
+ cisRoot: this.cisRoot,
+ hasLehreUnterrichtszeitenTypWPermission:
+ this.permissions["lehre/unterrichtszeiten_typ_w"] || false,
+ };
+ },
+ data: () => {
+ return {
+ phrasesLoaded: false,
+ editorParams: null,
+ classTimeSlotValidityPeriods: [],
+ classTimeSlotValidityPeriodId: null,
+ editedClassTimeSlotValidityPeriodId: null,
+ mondayClassTimeSlots: [],
+ isClassTimeSlotTypeModalVisible: false,
+ isClassTimeSlotValidityPeriodModalVisible: false,
+ };
+ },
+ computed: {
+ tabulatorOptions() {
+ const options = {
+ ajaxURL: "dummy",
+ ajaxRequestFunc: () =>
+ this.$api.call(ApiClassSchedule.getAllClassTimeValidityPeriods()),
+ ajaxResponse: (url, params, response) => response.data,
+ persistenceID: "core_class_schedule_validity_periods",
+ selectableRows: true,
+ columns: [
+ {
+ title: "gueltig von",
+ field: "gueltig_von",
+ width: 150,
+ },
+ { title: "gueltig bis", field: "gueltig_bis", width: 150 },
+ { title: "orgform kurzbz", field: "orgform_kurzbz", width: 150 },
+ {
+ title: "ausbildungssemester",
+ field: "ausbildungssemester",
+ width: 150,
+ },
+ {
+ title: "typ",
+ field: "unterrichtszeitentyp_kurzbz",
+ },
+ {
+ field: "unterrichtszeitengueltigkeit_id",
+ visible: false,
+ },
+ {
+ title: "Aktionen",
+ field: "actions",
+ minWidth: 150,
+ maxWidth: 150,
+ formatter: (cell, formatterParams, onRendered) => {
+ let container = document.createElement("div");
+ container.className = "d-flex gap-2";
+
+ let button = document.createElement("button");
+
+ button = document.createElement("button");
+ button.className = "btn btn-outline-secondary btn-action";
+ button.innerHTML = '';
+ button.title = this.$p.t(
+ "classSchedule",
+ "btn_showClassTimeSlotValidityPeriod",
+ );
+ button.addEventListener("click", (event) =>
+ this.$router.push({
+ name: "validityPeriodOverview",
+ params: {
+ classTimeSlotValidityPeriodId:
+ cell.getData().unterrichtszeitengueltigkeit_id,
+ },
+ }),
+ );
+ container.append(button);
+
+ button = document.createElement("button");
+ button.className = "btn btn-outline-secondary btn-action";
+ button.innerHTML = '';
+ button.title = this.$p.t(
+ "classSchedule",
+ "btn_editClassTimeSlotValidityPeriod",
+ );
+ button.addEventListener("click", (event) =>
+ this.editClassTimeSlotValidityPeriod(
+ cell.getData().unterrichtszeitengueltigkeit_id,
+ ),
+ );
+ container.append(button);
+
+ button = document.createElement("button");
+ button.className =
+ "btn btn-outline-secondary btn-action bg-danger";
+ button.innerHTML = '';
+ button.title = this.$p.t(
+ "classSchedule",
+ "btn_deleteClassTimeSlotValidityPeriod",
+ );
+ button.addEventListener("click", () => {
+ let isDeletionConfirmed = confirm(
+ this.$p.t(
+ "ui",
+ "deleteClassTimeSlotValidityPeriodConfirmation",
+ ),
+ );
+ if (!isDeletionConfirmed) return;
+
+ this.deleteClassTimeSlotValidityPeriod(
+ cell.getData().unterrichtszeitengueltigkeit_id,
+ );
+ });
+ container.append(button);
+
+ return container;
+ },
+ frozen: true,
+ },
+ ],
+ };
+ return options;
+ },
+ tabulatorEvents() {
+ const events = [
+ {
+ event: "tableBuilt",
+ handler: async () => {
+ const setHeader = (field, text) => {
+ const col =
+ this.$refs.classTimeSlotValidityPeriodsTable.tabulator.getColumn(
+ field,
+ );
+ if (!col) return;
+
+ const el = col.getElement();
+ if (!el || !el.querySelector) return;
+
+ const titleEl = el.querySelector(".tabulator-col-title");
+ if (titleEl) {
+ titleEl.textContent = text;
+ }
+ };
+
+ setHeader("nummer", this.$p.t("wawi", "nummer"));
+ setHeader("anmerkung", this.$p.t("global", "anmerkung"));
+ setHeader("retouram", this.$p.t("wawi", "retourdatum"));
+ setHeader("beschreibung", this.$p.t("global", "beschreibung"));
+ setHeader("kaution", this.$p.t("infocenter", "kaution"));
+ setHeader("ausgegebenam", this.$p.t("wawi", "ausgabedatum"));
+ setHeader("person_id", this.$p.t("person", "person_id"));
+ setHeader("uid", this.$p.t("person", "uid"));
+ },
+ },
+ ];
+ return events;
+ },
+ },
+ methods: {
+ showClassTimeSlotValidityPeriodModal() {
+ this.isClassTimeSlotValidityPeriodModalVisible = true;
+ },
+ editClassTimeSlotValidityPeriod(classTimeSlotValidityPeriodId) {
+ this.editedClassTimeSlotValidityPeriodId = classTimeSlotValidityPeriodId;
+ },
+ deleteClassTimeSlotValidityPeriod(classTimeSlotValidityPeriodId) {
+ return this.$api
+ .call(
+ ApiClassSchedule.deleteClassTimeSlotValidityPeriod(
+ this.id,
+ classTimeSlotValidityPeriodId,
+ ),
+ )
+ .then((response) => {
+ this.$fhcAlert.alertSuccess(this.$p.t("ui", "successDelete"));
+ window.scrollTo(0, 0);
+ this.$refs.classTimeSlotValidityPeriodsTable.reloadTable();
+ })
+ .catch((error) => {
+ console.error(
+ "Error deleting class time slot validity period:",
+ error,
+ );
+ this.$fhcAlert.handleSystemError(error);
+ });
+ },
+ showClassTimeSlotTypeModal() {
+ this.isClassTimeSlotTypeModalVisible = true;
+ },
+ resetClassTimeSlotTypeModal() {
+ this.isClassTimeSlotTypeModalVisible = false;
+ },
+ resetClassTimeSlotValidityPeriodModal() {
+ this.isClassTimeSlotValidityPeriodModalVisible = false;
+ },
+ },
+ async created() {
+ let getAllClassTimeValidityPeriodsResponse = await this.$api.call(
+ ApiClassSchedule.getAllClassTimeValidityPeriods(),
+ );
+ if (getAllClassTimeValidityPeriodsResponse.meta.status === "success") {
+ this.classTimeSlotValidityPeriods =
+ getAllClassTimeValidityPeriodsResponse.data;
+ } else {
+ console.error(
+ "Error fetching class time slot validity periods:",
+ getAllClassTimeValidityPeriodsResponse.meta.message,
+ );
+ }
+ },
+ mounted() {
+ this.$p
+ .loadCategory(["global", "lehre", "ui", "gruppenmanagement"])
+ .then(() => {
+ this.phrasesLoaded = true;
+ });
+ },
+ template: `
+
+
{{ $p.t("ui", "classScheduleOverviewHeading") }}
+
+
+
{ resetClassTimeSlotValidityPeriodModal(); editedClassTimeSlotValidityPeriodId = null; }"
+ @classTimeSlotValidityPeriodCreated="() => {
+ $refs.classTimeSlotValidityPeriodsTable.reloadTable();
+ resetClassTimeSlotValidityPeriodModal();
+ this.editedClassTimeSlotValidityPeriodId = null;
+ }"
+ @classTimeSlotValidityPeriodUpdated="() => {
+ $refs.classTimeSlotValidityPeriodsTable.reloadTable();
+ resetClassTimeSlotValidityPeriodModal();
+ this.editedClassTimeSlotValidityPeriodId = null;
+ }"
+ />
+
+
+ `,
+};
diff --git a/public/js/components/ClassSchedule/ClassScheduleTypeModal.js b/public/js/components/ClassSchedule/ClassScheduleTypeModal.js
new file mode 100644
index 000000000..31cec71da
--- /dev/null
+++ b/public/js/components/ClassSchedule/ClassScheduleTypeModal.js
@@ -0,0 +1,252 @@
+import { CoreFilterCmpt } from "../filter/Filter.js";
+import ApiClassSchedule from "../../../js/api/factory/classSchedule.js";
+
+import BsModal from "../Bootstrap/Modal.js";
+import CoreForm from "../Form/Form.js";
+import FormInput from "../Form/Input.js";
+
+export default {
+ name: "ClassScheduleValidityPeriodForm",
+ components: {
+ BsModal,
+ CoreForm,
+ FormInput,
+ },
+ inject: {
+ hasLehreUnterrichtszeitenTypWPermission:
+ "hasLehreUnterrichtszeitenTypWPermission",
+ },
+ props: {
+ isVisible: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ watch: {
+ isVisible(newValue) {
+ if (newValue) {
+ this.$refs.classScheduleTypeModal.show();
+ } else {
+ this.$refs.classScheduleTypeModal.hide();
+ }
+ },
+ },
+ data: () => {
+ return {
+ isFormVisible: false,
+ isEditInProgress: false,
+ editedClassScheduleType: null,
+ classTimeSlotTypeFormData: {
+ isActive: true,
+ shortCode: "",
+ descriptions: [
+ { lang: "de", value: "" },
+ { lang: "en", value: "" },
+ ],
+ },
+ classScheduleTypes: [],
+ };
+ },
+ methods: {
+ async getAllClassScheduleTypes() {
+ let getAllClassScheduleTypeResponse = await this.$api.call(
+ ApiClassSchedule.getAllClassScheduleTypes(),
+ );
+ 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 });
+ }
+ return {
+ ...type,
+ bezeichnung_mehrsprachig: descriptions,
+ };
+ },
+ );
+ } else {
+ console.error(
+ "Error fetching class schedule types:",
+ getAllClassScheduleTypeResponse.meta.message,
+ );
+ }
+ },
+ createClassTimeSlotType() {
+ return this.$refs.classTimeSlotTypeForm
+ .call(
+ ApiClassSchedule.createClassTimeSlotType({
+ isActive: this.classTimeSlotTypeFormData.isActive,
+ shortCode: this.classTimeSlotTypeFormData.shortCode,
+ descriptions: this.classTimeSlotTypeFormData.descriptions,
+ }),
+ )
+ .then((response) => {
+ this.$fhcAlert.alertSuccess(this.$p.t("ui", "successSave"));
+ this.$emit("classTimeSlotTypeCreated");
+ this.getAllClassScheduleTypes();
+ this.resetClassTimeSlotTypeForm();
+ });
+ },
+ editClassTimeSlotType(classScheduleType) {
+ this.isEditInProgress = true;
+ this.isFormVisible = true;
+ this.editedClassScheduleType = classScheduleType;
+ this.classTimeSlotTypeFormData = {
+ isActive: classScheduleType.aktiv,
+ shortCode: classScheduleType.unterrichtszeitentyp_kurzbz,
+ descriptions: classScheduleType.bezeichnung_mehrsprachig.map(
+ (desc) => ({
+ lang: desc.lang,
+ value: desc.value,
+ }),
+ ),
+ };
+ },
+ updateClassTimeSlotType() {
+ return this.$refs.classTimeSlotTypeForm
+ .call(
+ ApiClassSchedule.updateClassTimeSlotType(
+ this.classTimeSlotTypeFormData.shortCode,
+ {
+ isActive: this.classTimeSlotTypeFormData.isActive,
+ descriptions: this.classTimeSlotTypeFormData.descriptions,
+ },
+ ),
+ )
+ .then((response) => {
+ this.$fhcAlert.alertSuccess(this.$p.t("ui", "successSave"));
+ this.$emit("classTimeSlotTypeUpdated");
+ this.getAllClassScheduleTypes();
+ this.resetClassTimeSlotTypeForm();
+ });
+ },
+ deleteClassTimeSlotType(classScheduleTypeShortCode) {
+ let isConfirmed = confirm(
+ this.$p.t("ui", "confirmDeleteClassTimeSlotType"),
+ );
+ if (!isConfirmed) {
+ return Promise.resolve();
+ }
+
+ return this.$api
+ .call(
+ ApiClassSchedule.deleteClassTimeSlotType(
+ this.id,
+ classScheduleTypeShortCode,
+ ),
+ )
+ .then((response) => {
+ this.$fhcAlert.alertSuccess(this.$p.t("ui", "successDelete"));
+ window.scrollTo(0, 0);
+ this.getAllClassScheduleTypes();
+ })
+ .catch((error) => {
+ console.error("Error deleting class time slot type:", error);
+ this.$fhcAlert.handleSystemError(error);
+ });
+ },
+ showClassTimeSlotTypeForm() {
+ this.isFormVisible = true;
+ },
+ hideClassTimeSlotTypeForm() {
+ this.resetClassTimeSlotTypeForm();
+ },
+ resetClassTimeSlotTypeForm() {
+ this.isEditInProgress = false;
+ this.isFormVisible = false;
+ this.classTimeSlotTypeFormData = {
+ isActive: true,
+ shortCode: "",
+ descriptions: [
+ { lang: "de", value: "" },
+ { lang: "en", value: "" },
+ ],
+ };
+ },
+ },
+ async created() {
+ await this.getAllClassScheduleTypes();
+ },
+ template: `
+ { $emit('hideBsModal'); resetClassTimeSlotTypeForm(); }">
+
+ {{$p.t('ui', 'editClassTimeSlotTypeModalTitle')}}
+ {{$p.t('ui', 'existingClassScheduleTypesLabel')}}
+
+
+
+
+
+
+
+
+
+
+
+
+
{{$p.t('ui/shortName')}}: {{editedClassScheduleType.unterrichtszeitentyp_kurzbz}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{$p.t('ui', 'existingClassScheduleTypesLabel')}}
+
+
+
+
+ {{classScheduleType.unterrichtszeitentyp_kurzbz}}
+
+
+
+
+ {{classScheduleType.bezeichnung_mehrsprachig[0].value}} / {{classScheduleType.bezeichnung_mehrsprachig[1].value}}
+
+
+
+
+
+ `,
+};
diff --git a/public/js/components/ClassSchedule/ClassScheduleValidityPeriodForm.js b/public/js/components/ClassSchedule/ClassScheduleValidityPeriodForm.js
new file mode 100644
index 000000000..2eeaf46a3
--- /dev/null
+++ b/public/js/components/ClassSchedule/ClassScheduleValidityPeriodForm.js
@@ -0,0 +1,241 @@
+import { CoreFilterCmpt } from "../filter/Filter.js";
+import ApiClassSchedule from "../../../js/api/factory/classSchedule.js";
+
+import BsModal from "../Bootstrap/Modal.js";
+import CoreForm from "../Form/Form.js";
+import FormInput from "../Form/Input.js";
+import ClassScheduleValidityPeriodFormTimeSlot from "./ClassScheduleValidityPeriodFormTimeSlot.js";
+
+export default {
+ name: "ClassScheduleValidityPeriodForm",
+ components: {
+ BsModal,
+ CoreForm,
+ FormInput,
+ CoreFilterCmpt,
+ ClassScheduleValidityPeriodFormTimeSlot,
+ },
+ props: {
+ classTimeSlotValidityPeriod: {
+ type: Object,
+ required: true,
+ },
+ editedClassTimeSlots: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ },
+ emits: ["hideForm", "classTimeSlotsCreated", "classTimeSlotsEdited"],
+ watch: {
+ editedClassTimeSlots: {
+ handler(newVal) {
+ console.log("editedClassTimeSlots changed:", newVal);
+ this.classTimeSlots = JSON.parse(JSON.stringify(newVal));
+ console.log(
+ "classTimeSlots updated from editedClassTimeSlots:",
+ this.classTimeSlots,
+ );
+ },
+ immediate: true,
+ },
+ },
+ data: () => {
+ return {
+ classTimeSlots: [],
+ classTimeSlotTypes: [],
+ };
+ },
+ computed: {
+ areClassTimeSlotsEdited() {
+ return this.editedClassTimeSlots && this.editedClassTimeSlots.length > 0;
+ },
+ },
+ methods: {
+ hideForm() {
+ this.$emit("hideForm");
+ this.classTimeSlots = [];
+ },
+ addClassTimeSlotPerDay(day) {
+ this.classTimeSlots.push({
+ identifier: Math.random().toString(36).substr(2, 9),
+ wochentag: this.getWeekdayNumberFromDay(day),
+ startTime: "08:00",
+ endTime: "10:00",
+ classTimeSlotTypeShortcode: null,
+ });
+ },
+ removeClassTimeSlotPerDay(day, identifier) {
+ this.classTimeSlots = this.classTimeSlots.filter(
+ (slot) =>
+ !(
+ slot.wochentag === this.getWeekdayNumberFromDay(day) &&
+ slot.identifier === identifier
+ ),
+ );
+ },
+ createClassTimeSlots() {
+ return this.$refs.classTimeSlotData
+ .call(
+ ApiClassSchedule.createClassTimeSlotsForValidityPeriod(
+ this.id,
+ this.$props.classTimeSlotValidityPeriod
+ .unterrichtszeitengueltigkeit_id,
+ {
+ classTimeSlots: this.classTimeSlots,
+ },
+ ),
+ )
+ .then((response) => {
+ this.$fhcAlert.alertSuccess(this.$p.t("ui", "successSave"));
+ this.classTimeSlots = [];
+ this.$emit("classTimeSlotsCreated");
+ })
+ .catch((error) => {
+ console.error(
+ "Error creating class time slot validity period:",
+ error,
+ );
+ this.$fhcAlert.handleSystemError(error);
+ });
+ },
+ editClassTimeSlots() {
+ return this.$refs.classTimeSlotData
+ .call(
+ ApiClassSchedule.editClassTimeSlotsForValidityPeriod(
+ this.id,
+ this.$props.classTimeSlotValidityPeriod
+ .unterrichtszeitengueltigkeit_id,
+ {
+ classTimeSlots: this.classTimeSlots,
+ },
+ ),
+ )
+ .then((response) => {
+ this.$fhcAlert.alertSuccess(this.$p.t("ui", "successSave"));
+ this.classTimeSlots = [];
+ this.$emit("classTimeSlotsEdited");
+ })
+ .catch((error) => {
+ console.error(
+ "Error editing class time slot validity period:",
+ error,
+ );
+ this.$fhcAlert.handleSystemError(error);
+ });
+ },
+ getWeekdayNumberFromDay(day) {
+ const days = {
+ monday: 1,
+ tuesday: 2,
+ wednesday: 3,
+ thursday: 4,
+ friday: 5,
+ saturday: 6,
+ sunday: 7,
+ };
+ return days[day.toLowerCase()] || null;
+ },
+ },
+ 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 {
+ console.error(
+ "Error fetching class time slot types:",
+ getAllClassTimeSlotTypesResponse.meta.message,
+ );
+ }
+ },
+ template: `
+
+
+
+
+
+
+
+
+ `,
+};
diff --git a/public/js/components/ClassSchedule/ClassScheduleValidityPeriodFormTimeSlot.js b/public/js/components/ClassSchedule/ClassScheduleValidityPeriodFormTimeSlot.js
new file mode 100644
index 000000000..575cd087c
--- /dev/null
+++ b/public/js/components/ClassSchedule/ClassScheduleValidityPeriodFormTimeSlot.js
@@ -0,0 +1,84 @@
+import { CoreFilterCmpt } from "../filter/Filter.js";
+import ApiClassSchedule from "../../../js/api/factory/classSchedule.js";
+
+import BsModal from "../Bootstrap/Modal.js";
+import CoreForm from "../Form/Form.js";
+import FormInput from "../Form/Input.js";
+
+export default {
+ name: "ClassScheduleValidityPeriodForm",
+ components: {
+ BsModal,
+ CoreForm,
+ FormInput,
+ CoreFilterCmpt,
+ },
+ props: {
+ modelValue: Object,
+ classTimeSlotTypes: Array,
+ },
+ emits: ["update:modelValue", "removeClassTimeSlot"],
+ data: () => {
+ return {
+ classTimeSlots: [],
+ };
+ },
+ computed: {
+ selectedClassTimeSlotType() {
+ return this.classTimeSlotTypes.find(
+ (type) =>
+ type.unterrichtszeitentyp_kurzbz ===
+ this.classTimeSlot.classTimeSlotTypeShortcode,
+ );
+ },
+ classTimeSlot: {
+ get() {
+ return this.modelValue;
+ },
+ set(value) {
+ this.$emit("update:modelValue", value);
+ },
+ },
+ },
+ methods: {},
+ template: `
+
+
+
+
+
+
+
+
+
+ `,
+};
diff --git a/public/js/components/ClassSchedule/ClassScheduleValidityPeriodModal.js b/public/js/components/ClassSchedule/ClassScheduleValidityPeriodModal.js
new file mode 100644
index 000000000..52cb765ed
--- /dev/null
+++ b/public/js/components/ClassSchedule/ClassScheduleValidityPeriodModal.js
@@ -0,0 +1,305 @@
+import ApiClassSchedule from "../../../js/api/factory/classSchedule.js";
+import ApiStudiengang from "../../../js/api/factory/studiengang.js";
+
+import BsModal from "../Bootstrap/Modal.js";
+import CoreForm from "../Form/Form.js";
+import FormInput from "../Form/Input.js";
+import FormValidation from "../Form/Validation.js";
+import ApiStudienPlan from "../../../js/api/factory/studienplan.js";
+
+export default {
+ name: "ClassScheduleValidityPeriodModal",
+ components: {
+ BsModal,
+ CoreForm,
+ FormInput,
+ FormValidation,
+ },
+ props: {
+ isVisible: {
+ type: Boolean,
+ required: true,
+ },
+ editedClassTimeSlotValidityPeriodId: {
+ type: Number,
+ default: null,
+ },
+ },
+ emits: [
+ "hideBsModal",
+ "classTimeSlotValidityPeriodCreated",
+ "classTimeSlotValidityPeriodUpdated",
+ ],
+ watch: {
+ isVisible(newValue) {
+ if (newValue) {
+ this.$refs.classTimeSlotValidityPeriodModal.show();
+ } else {
+ this.$refs.classTimeSlotValidityPeriodModal.hide();
+ }
+ },
+ editedClassTimeSlotValidityPeriodId(newValue) {
+ if (!newValue) return;
+
+ return this.$api
+ .call(ApiClassSchedule.getClassTimeValidityPeriod(newValue))
+ .then((response) => {
+ let validityPeriodData = response.data[0];
+ this.classTimeSlotValidityPeriodFormData = {
+ id: validityPeriodData.unterrichtszeitengueltigkeit_id,
+ degreeProgramShortcode: validityPeriodData.oe_kurzbz,
+ studyPlanId: validityPeriodData.studienplan_id,
+ classTimeSlotTypeShortcode:
+ validityPeriodData.unterrichtszeitentyp_kurzbz,
+ validityPeriodFrom: validityPeriodData.gueltig_von,
+ validityPeriodTo: validityPeriodData.gueltig_bis,
+ semester: validityPeriodData.ausbildungssemester,
+ description: validityPeriodData.anmerkung,
+ };
+ this.$refs.classTimeSlotValidityPeriodModal.show();
+ })
+ .catch((error) => {
+ console.error(
+ "Error fetching class time slot validity period details:",
+ error,
+ );
+ this.$fhcAlert.handleSystemError(error);
+ });
+ },
+ },
+ data: () => {
+ return {
+ isFormVisible: false,
+ isEditInProgress: false,
+ degreePrograms: [],
+ studyPlans: [],
+ classTimeSlotTypes: [],
+ classTimeSlotValidityPeriodFormData: {
+ id: null,
+ degreeProgramShortcode: null,
+ studyPlanId: null,
+ classTimeSlotTypeShortcode: null,
+ validityPeriodFrom: null,
+ validityPeriodTo: null,
+ semester: null,
+ description: null,
+ },
+ };
+ },
+ methods: {
+ createClassTimeSlotValidityPeriod() {
+ return this.$refs.classTimeSlotValidityPeriodData
+ .call(
+ ApiClassSchedule.createClassTimeSlotValidityPeriod(
+ this.id,
+ this.classTimeSlotValidityPeriodFormData,
+ ),
+ )
+ .then((response) => {
+ this.$fhcAlert.alertSuccess(this.$p.t("ui", "successSave"));
+ this.$refs.classTimeSlotValidityPeriodModal.hide();
+ this.resetClassTimeSlotValidityPeriodModal();
+ window.scrollTo(0, 0);
+ this.$emit("classTimeSlotValidityPeriodCreated");
+ })
+ .catch((error) => {
+ console.error(
+ "Error creating class time slot validity period:",
+ error,
+ );
+ this.$fhcAlert.handleSystemError(error);
+ });
+ },
+ updateClassTimeSlotValidityPeriod() {
+ return this.$refs.classTimeSlotValidityPeriodData
+ .call(
+ ApiClassSchedule.updateClassTimeSlotValidityPeriod(
+ this.id,
+ this.classTimeSlotValidityPeriodFormData.id,
+ this.classTimeSlotValidityPeriodFormData,
+ ),
+ )
+ .then((response) => {
+ this.$fhcAlert.alertSuccess(this.$p.t("ui", "successSave"));
+ this.$refs.classTimeSlotValidityPeriodModal.hide();
+ this.resetClassTimeSlotValidityPeriodModal();
+ window.scrollTo(0, 0);
+ this.$emit("classTimeSlotValidityPeriodUpdated");
+ })
+ .catch((error) => {
+ console.error(
+ "Error updating class time slot validity period:",
+ error,
+ );
+ this.$fhcAlert.handleSystemError(error);
+ });
+ },
+ resetClassTimeSlotValidityPeriodModal() {
+ this.$refs.classTimeSlotValidityPeriodData?.clearValidation();
+ this.classTimeSlotValidityPeriodFormData = {
+ id: null,
+ degreeProgramShortcode: null,
+ studyPlanId: null,
+ classTimeSlotTypeShortcode: null,
+ validityPeriodFrom: null,
+ validityPeriodTo: null,
+ semester: null,
+ description: null,
+ };
+ },
+ },
+ async created() {
+ let getAllDegreeProgramsResponse = await this.$api.call(
+ ApiStudiengang.getAllDegreePrograms(),
+ );
+ if (getAllDegreeProgramsResponse.meta.status === "success") {
+ this.degreePrograms = getAllDegreeProgramsResponse.data;
+ } else {
+ console.error(
+ "Error fetching degree programs:",
+ getAllDegreeProgramsResponse.meta.message,
+ );
+ }
+
+ let getAllStudyPlansResponse = await this.$api.call(
+ ApiStudienPlan.getAllStudyPlans(),
+ );
+ if (getAllStudyPlansResponse.meta.status === "success") {
+ this.studyPlans = getAllStudyPlansResponse.data;
+ } else {
+ console.error(
+ "Error fetching study plans:",
+ getAllStudyPlansResponse.meta.message,
+ );
+ }
+
+ 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 {
+ console.error(
+ "Error fetching class time slot types:",
+ getAllClassTimeSlotTypesResponse.meta.message,
+ );
+ }
+ },
+ template: `
+ { $emit('hideBsModal'); resetClassTimeSlotValidityPeriodModal(); }" size="md">
+
+ {{$p.t('ui', 'addClassTimeSlotValidityPeriodModalTitle')}}
+ {{$p.t('ui', 'editClassTimeSlotValidityPeriodModalTitle')}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+};
diff --git a/public/js/components/ClassSchedule/ClassScheduleValidityPeriodOverview.js b/public/js/components/ClassSchedule/ClassScheduleValidityPeriodOverview.js
new file mode 100644
index 000000000..7b393b41f
--- /dev/null
+++ b/public/js/components/ClassSchedule/ClassScheduleValidityPeriodOverview.js
@@ -0,0 +1,386 @@
+import { CoreFilterCmpt } from "../filter/Filter.js";
+import ApiClassSchedule from "../../../js/api/factory/classSchedule.js";
+
+import BsModal from "../Bootstrap/Modal.js";
+import CoreForm from "../Form/Form.js";
+import FormInput from "../Form/Input.js";
+import ClassScheduleValidityPeriodForm from "./ClassScheduleValidityPeriodForm.js";
+import ClassScheduleValidityPeriodModal from "./ClassScheduleValidityPeriodModal.js";
+
+export default {
+ name: "ClassScheduleValidityPeriodOverview",
+ components: {
+ BsModal,
+ CoreForm,
+ FormInput,
+ CoreFilterCmpt,
+ ClassScheduleValidityPeriodForm,
+ ClassScheduleValidityPeriodModal,
+ },
+ data: () => {
+ return {
+ isClassTimeSlotFormVisible: false,
+ classTimeSlotValidityPeriodId: null,
+ classTimeSlotValidityPeriod: null,
+ classTimeSlots: [],
+ classTimeSlotTypes: [],
+ editedClassTimeSlots: [],
+ isClassTimeSlotValidityPeriodModalVisible: false,
+ editedClassTimeSlotValidityPeriodId: null,
+ };
+ },
+ computed: {
+ classScheduleValidityPeriodStartDate() {
+ if (!this.classTimeSlotValidityPeriod) return null;
+
+ let dateParts = this.classTimeSlotValidityPeriod.gueltig_von
+ .split("-")
+ .reverse();
+ return dateParts.join("/");
+ },
+ classScheduleValidityPeriodEndDate() {
+ if (!this.classTimeSlotValidityPeriod) return null;
+ let dateParts = this.classTimeSlotValidityPeriod.gueltig_bis
+ .split("-")
+ .reverse();
+ return dateParts.join("/");
+ },
+ },
+ methods: {
+ async fetchClassTimeValidityPeriod() {
+ let getClassTimeValidityPeriodResponse = await this.$api.call(
+ ApiClassSchedule.getClassTimeValidityPeriod(
+ this.classTimeSlotValidityPeriodId,
+ ),
+ );
+ if (getClassTimeValidityPeriodResponse.meta.status === "success") {
+ this.classTimeSlotValidityPeriod =
+ getClassTimeValidityPeriodResponse.data[0];
+
+ if (!this.classTimeSlotValidityPeriod) {
+ this.$fhcAlert.alertError(
+ this.$p.t("ui", "classTimeSlotValidityPeriodNotFound"),
+ );
+ this.$router.push({ name: "overview" });
+ }
+ } else {
+ console.error(
+ "Error fetching class time slot validity period:",
+ getClassTimeValidityPeriodResponse.meta.message,
+ );
+ }
+ },
+ async fetchClassTimeSlots() {
+ let getClassTimeSlotsForValidityPeriodResponse = await this.$api.call(
+ ApiClassSchedule.getClassTimeSlotsForValidityPeriod(
+ this.classTimeSlotValidityPeriodId,
+ ),
+ );
+ 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;
+ } else {
+ console.error(
+ "Error fetching class time slots for validity period:",
+ this.classTimeSlots,
+ );
+ }
+ },
+ showClassTimeSlotForm() {
+ this.isClassTimeSlotFormVisible = true;
+ },
+ async editClassTimeSlotsForValidityPeriodPerGroup(groupIdentifikator) {
+ console.log("Editing class time slots for group:", groupIdentifikator);
+ await this.fetchClassTimeSlots();
+ console.log(
+ this.classTimeSlots.filter(
+ (group) => group.groupIdentifikator === groupIdentifikator,
+ ),
+ );
+ 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,
+ };
+ }) || [];
+ console.log(this.editedClassTimeSlots);
+ this.showClassTimeSlotForm();
+ },
+ deleteClassTimeSlotsForValidityPeriodPerGroup(groupIdentifikator) {
+ let isDeletionConfirmed = confirm(
+ this.$p.t("ui", "confirmDeleteClassTimeSlotsForGroup"),
+ );
+ if (!isDeletionConfirmed) {
+ return;
+ }
+
+ return this.$api
+ .call(
+ ApiClassSchedule.deleteClassTimeSlotsForValidityPeriodPerGroup(
+ this.id,
+ this.classTimeSlotValidityPeriodId,
+ groupIdentifikator,
+ ),
+ )
+ .then((response) => {
+ this.$fhcAlert.alertSuccess(this.$p.t("ui", "successDelete"));
+ this.classTimeSlots = [];
+ this.fetchClassTimeSlots();
+ })
+ .catch((error) => {
+ console.error("Error deleting class time slots per group:", error);
+ this.$fhcAlert.handleSystemError(error);
+ });
+ },
+ getClassTimeSlotType(classTimeSlot) {
+ let classTimeSlotType = this.classTimeSlotTypes.find(
+ (type) =>
+ type.unterrichtszeitentyp_kurzbz ===
+ classTimeSlot.unterrichtszeitentyp_kurzbz,
+ );
+
+ return classTimeSlotType;
+ },
+ getClassTimeSlotBackgroundColor(classTimeSlot) {
+ let classTimeSlotType = this.classTimeSlotTypes.find(
+ (type) =>
+ type.unterrichtszeitentyp_kurzbz ===
+ classTimeSlot.unterrichtszeitentyp_kurzbz,
+ );
+
+ return classTimeSlotType ? classTimeSlotType.hintergrundfarbe : "#fff";
+ },
+ editClassTimeSlotValidityPeriod(classTimeSlotValidityPeriodId) {
+ this.editedClassTimeSlotValidityPeriodId = classTimeSlotValidityPeriodId;
+ },
+ deleteClassTimeSlotValidityPeriod(classTimeSlotValidityPeriodId) {
+ let isDeletionConfirmed = confirm(
+ this.$p.t("ui", "deleteClassTimeSlotValidityPeriodConfirmation"),
+ );
+ if (!isDeletionConfirmed) return;
+
+ return this.$api
+ .call(
+ ApiClassSchedule.deleteClassTimeSlotValidityPeriod(
+ this.id,
+ classTimeSlotValidityPeriodId,
+ ),
+ )
+ .then((response) => {
+ this.$fhcAlert.alertSuccess(this.$p.t("ui", "successDelete"));
+ this.$router.push({ name: "overview" });
+ })
+ .catch((error) => {
+ console.error(
+ "Error deleting class time slot validity period:",
+ error,
+ );
+ this.$fhcAlert.handleSystemError(error);
+ });
+ },
+ resetClassTimeSlotValidityPeriodModal() {
+ this.isClassTimeSlotValidityPeriodModalVisible = false;
+ },
+ },
+ async created() {
+ this.classTimeSlotValidityPeriodId =
+ this.$route.params.classTimeSlotValidityPeriodId;
+
+ this.fetchClassTimeValidityPeriod();
+
+ 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 {
+ console.error(
+ "Error fetching class time slot types:",
+ getAllClassTimeSlotTypesResponse.meta.message,
+ );
+ }
+
+ this.fetchClassTimeSlots();
+ },
+ template: `
+
+
+
+
+
+ {{ $p.t("ui", "classScheduleValidityPeriodOverviewHeading") }}
+
+ {{ classTimeSlotValidityPeriod?.orgform_kurzbz }}
+
+
+
+
{{ classScheduleValidityPeriodStartDate }} - {{ classScheduleValidityPeriodEndDate }}
+
+
+
+
+
+
+
{ isClassTimeSlotFormVisible = false; fetchClassTimeSlots(); this.editedClassTimeSlots = []; }"
+ @classTimeSlotsEdited="() => { isClassTimeSlotFormVisible = false; fetchClassTimeSlots(); this.editedClassTimeSlots = []; }"
+ @hideForm="() => { isClassTimeSlotFormVisible = false; this.editedClassTimeSlots = []; }"
+ class="mb-4"
+ />
+
+
{{ $p.t("ui", "classScheduleValidityPeriodTimeSlots") }}
+
+
+
+
+
+
Monday
+
+
+ {{ classTimeSlot.uhrzeit_von }} - {{ classTimeSlot.uhrzeit_bis }}
+
+
+
+
+
Tuesday
+
+
+ {{ classTimeSlot.uhrzeit_von }} - {{ classTimeSlot.uhrzeit_bis }}
+
+
+
+
+
Wednesday
+
+
+ {{ classTimeSlot.uhrzeit_von }} - {{ classTimeSlot.uhrzeit_bis }}
+
+
+
+
+
Thursday
+
+
+ {{ classTimeSlot.uhrzeit_von }} - {{ classTimeSlot.uhrzeit_bis }}
+
+
+
+
+
Friday
+
+
+ {{ classTimeSlot.uhrzeit_von }} - {{ classTimeSlot.uhrzeit_bis }}
+
+
+
+
+
Saturday
+
+
+ {{ classTimeSlot.uhrzeit_von }} - {{ classTimeSlot.uhrzeit_bis }}
+
+
+
+
+
Sunday
+
+
+ {{ classTimeSlot.uhrzeit_von }} - {{ classTimeSlot.uhrzeit_bis }}
+
+
+
+
+
+
+
{{ $p.t("ui", "noClassScheduleValidityPeriodTimeSlotsFound") }}
+
+
+
{ resetClassTimeSlotValidityPeriodModal(); editedClassTimeSlotValidityPeriodId = null; }"
+ @classTimeSlotValidityPeriodUpdated="() => {
+ resetClassTimeSlotValidityPeriodModal();
+ this.editedClassTimeSlotValidityPeriodId = null;
+ fetchClassTimeValidityPeriod();
+ }"
+ />
+
+ `,
+};
diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php
index f7628f8ef..2031a3794 100644
--- a/system/phrasesupdate.php
+++ b/system/phrasesupdate.php
@@ -57276,6 +57276,586 @@ I have been informed that I am under no obligation to consent to the transmissio
)
)
),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'montag',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Montag',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Monday',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'dienstag',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Dienstag',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Tuesday',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'mittwoch',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Mittwoch',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Wednesday',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'donnerstag',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Donnerstag',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Thursday',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'freitag',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Freitag',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Friday',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'samstag',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Samstag',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Saturday',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'sonntag',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Sonntag',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Sunday',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'deleteClassTimeSlotValidityPeriodConfirmation',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Möchten Sie wirklich diese Unterrichtszeitgültigkeit löschen?',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Do you really want to delete this class time slot validity period?',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'classScheduleValidityPeriodOverviewHeading',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Übersicht Unterrichtszeitgültigkeiten',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Class Time Slot Validity Period Overview',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'addClassTimeSlotValidityPeriodModalTitle',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Unterrichtszeitgültigkeit hinzufügen',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Add Class Time Slot Validity Period',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'addClassTimeSlotValidityPeriodButton',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Unterrichtszeitgültigkeit hinzufügen',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Add Class Time Slot Validity Period',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'editClassTimeSlotValidityPeriodModalTitle',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Unterrichtszeitgültigkeit bearbeiten',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Edit Class Time Slot Validity Period',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'confirmDeleteClassTimeSlotsForGroup',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Möchten Sie wirklich diese Unterrichtszeiten löschen?',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Do you really want to delete these class time slots?',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'addClassTimeSlotTypeModalTitle',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Unterrichtszeittyp hinzufügen',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Add Class Time Slot Type',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'editClassTimeSlotTypeModalTitle',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Unterrichtszeittyp bearbeiten',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Edit Class Time Slot Type',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'shortName',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'kurzbezeichnung',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'short name',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'description',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'beschreibung',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'description',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'existingClassScheduleTypesLabel',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Vorhandene Unterrichtszeittypen',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Existing Class Schedule Types',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'confirmDeleteClassTimeSlotType',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Möchten Sie wirklich diesen Unterrichtszeittyp löschen?',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Do you really want to delete this class time slot type?',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'classTimeSlotType',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Unterrichtszeittyp',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Class Time Slot Type',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'isActive',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Aktiv',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Active',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'validityPeriod',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Gültigkeitszeitraum',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Validity Period',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'addClassTimeSlotTypeButton',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Unterrichtszeittyp hinzufügen',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Add Class Time Slot Type',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'addClassTimeSlotTypeButton',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Unterrichtszeittyp hinzufügen',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Add Class Time Slot Type',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'classScheduleValidityPeriodTimeSlots',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Unterrichtszeiten für diesen Gültigkeitszeitraum',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Class time slots for this validity period',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'noClassScheduleValidityPeriodTimeSlotsFound',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Keine Unterrichtszeiten für diesen Gültigkeitszeitraum gefunden',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'No class time slots found for this validity period',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'classScheduleOverviewHeading',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Übersicht Unterrichtszeiten',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Class Schedule Overview',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'addClassTimeSlotButton',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Unterrichtszeit hinzufügen',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Add Class Time Slot',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'classTimeSlotValidityPeriodNotFound',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Kein Gültigkeitszeitraum für diese Unterrichtszeit gefunden',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'No validity period found for this class time slot',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ )
+ ),
// ### Phrases Dashboard Admin END
);