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") }}

+ + + + +
+ `, +}; 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: ` + + +
+
+ +
+
+ +
+ + +
+
+

{{$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: ` + +
+
+
+

Monday

+ +
+ +
+
+
+

Tuesday

+ +
+ +
+
+
+

Wednesday

+ +
+ +
+
+
+

Thursday

+ +
+ +
+
+
+

Friday

+ +
+ +
+
+
+ + + +
+
+ `, +}; 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: ` + + + + +
+ + + +
+
+ + + +
+
+ + + +
+
+
+ +
+
+ +
+
+ +
+
+
+ + + +
+
+ + +
+
+ + +
+ `, +}; 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 }}

+

+
+
+
+ +
+ +
+

{{ $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") }}

+
+
+ +
+ `, +}; 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 );