diff --git a/application/controllers/api/frontend/v1/education/Ferien.php b/application/controllers/api/frontend/v1/education/Ferien.php
new file mode 100644
index 000000000..419164fa8
--- /dev/null
+++ b/application/controllers/api/frontend/v1/education/Ferien.php
@@ -0,0 +1,192 @@
+.
+ */
+
+if (!defined('BASEPATH')) exit('No direct script access allowed');
+
+//use CI3_Events as Events;
+
+/**
+ * This controller operates between (interface) the JS (GUI) and the back-end
+ * This controller works with JSON calls on the HTTP GET or POST and the output is always JSON
+ */
+class Ferien extends FHCAPI_Controller
+{
+ /**
+ * Calls the parent's constructor and prepares libraries and phrases
+ */
+ public function __construct()
+ {
+ parent::__construct([
+ 'getFerien' => 'basis/ferien:r',
+ 'getStg' => 'basis/ferien:r',
+ 'insert' => 'basis/ferien:w',
+ 'delete' => 'basis/ferien:w'
+ ]);
+
+ // Load models
+ $this->load->model('organisation/Ferien_model', 'FerienModel');
+
+ // Load language phrases
+ $this->loadPhrases([
+ 'ui'
+ ]);
+ }
+
+ //------------------------------------------------------------------------------------------------------------------
+ // Public methods
+
+ /**
+ * Get Ferien
+ *
+ * @return void
+ */
+ public function getFerien()
+ {
+ $studiengang_kz = $this->input->get('studiengang_kz');
+
+ $this->addMeta('stgkz', $studiengang_kz);
+
+ if (!isset($studiengang_kz)) $this->terminateWithSuccess([]);
+
+ //~ if (isset($studiengang_kz) && !is_numeric($studiengang_kz))
+ //~ $this->terminateWithError($this->p->t('ui', 'errorMissingOrInvalidParameters', ['parameter'=> 'Studiengang']), self::ERROR_TYPE_GENERAL);
+
+ $this->FerienModel->addSelect('tbl_ferien.*, , UPPER(typ::varchar(1) || kurzbz) AS studiengang_kuerzel');
+ $this->FerienModel->addJoin('public.tbl_studiengang', 'studiengang_kz');
+
+ if (isset($studiengang_kz) && is_numeric($studiengang_kz))
+ $this->FerienModel->db->where('studiengang_kz', $studiengang_kz);
+
+ $this->FerienModel->addOrder('vondatum', 'DESC');
+ $result = $this->FerienModel->load();
+
+ $data = $this->getDataOrTerminateWithError($result);
+
+ $this->terminateWithSuccess($data);
+ }
+
+ /**
+ * Get list of Studiengaenge
+ *
+ * @return void
+ */
+ public function getStg()
+ {
+ $this->load->model('organisation/Studiengang_model', 'StudiengangModel');
+
+ $this->StudiengangModel->addSelect(' tbl_studiengang.*, UPPER(typ::varchar(1) || kurzbz) AS kuerzel');
+ $this->StudiengangModel->addOrder('typ, kurzbz');
+ $result = $this->StudiengangModel->load();
+
+ $data = $this->getDataOrTerminateWithError($result);
+
+ $this->terminateWithSuccess($data);
+ }
+
+ /**
+ * Save Ferien
+ *
+ * @return void
+ */
+ public function insert()
+ {
+ $this->load->library('form_validation');
+
+ $this->form_validation->set_rules('vondatum', 'Von Datum', 'required|is_valid_date');
+ $this->form_validation->set_rules('bisdatum', 'Bis Datum', 'required|is_valid_date');
+ $this->form_validation->set_rules('bezeichnung', 'Bezeichnung', 'required|max_length[128]');
+ $this->form_validation->set_rules('studiengang_kz', 'Studiengang', 'required|numeric');
+
+ //Events::trigger('konto_insert_validation', $this->form_validation);
+
+ if (!$this->form_validation->run())
+ $this->terminateWithValidationErrors($this->form_validation->error_array());
+
+ $allowed = [
+ 'vondatum',
+ 'bisdatum',
+ 'bezeichnung',
+ 'studiengang_kz'
+ ];
+
+ $data = [];
+
+ // TODO add insertaum and updateamum?
+ //~ $data = [
+ //~ 'insertamum' => date('c'),
+ //~ 'insertvon' => getAuthUID()
+ //~ ];
+ foreach ($allowed as $field)
+ {
+ if ($this->input->post($field) !== null) $data[$field] = $this->input->post($field);
+ }
+
+ $result = [];
+
+ $id = $this->getDataOrTerminateWithError($this->FerienModel->insert($data));
+
+ $this->terminateWithSuccess(hasData($id) ? getData($id) : null);
+ }
+
+ /**
+ * Delete Ferien
+ *
+ * @return void
+ */
+ public function delete()
+ {
+ $this->load->library('form_validation');
+
+ $this->form_validation->set_rules('ferien_id', 'Ferien Id', 'required');
+
+ if (!$this->form_validation->run())
+ $this->terminateWithValidationErrors($this->form_validation->error_array());
+
+ $ferien_id = $this->input->post('ferien_id');
+
+ $this->FerienModel->addSelect('ferien_id');
+ $result = $this->FerienModel->load($ferien_id);
+ $this->addMeta('res', $result);
+
+ if (!hasData($result))
+ $this->terminateWithError($this->p->t('ferien', 'error_missing', [
+ 'ferien_id' => $ferien_id
+ ]));
+
+ //~ $_POST['studiengang_kz'] = current($result)->studiengang_kz;
+
+ //~ $this->form_validation->set_rules('studiengang_kz', 'Studiengang', 'has_permissions_for_stg[admin:rw,assistenz:rw]');
+
+ //~ Events::trigger('konto_delete_validation', $this->form_validation);
+
+ //~ if (!$this->form_validation->run())
+ //~ $this->terminateWithValidationErrors($this->form_validation->error_array());
+
+
+ //Events::trigger('konto_delete', $ferien_id);
+
+ $result = $this->getDataOrTerminateWithError($this->FerienModel->delete($ferien_id));
+ //~ if (isError($result)) {
+ //~ if (getCode($result) != 42)
+ //~ $this->terminateWithError(getError($result));
+ //~ $this->terminateWithError($this->p->t('konto', 'error_delete_level'));
+ //~ }
+
+ $this->terminateWithSuccess();
+ }
+}
diff --git a/application/controllers/lehre/Ferienverwaltung.php b/application/controllers/lehre/Ferienverwaltung.php
new file mode 100644
index 000000000..6d531579a
--- /dev/null
+++ b/application/controllers/lehre/Ferienverwaltung.php
@@ -0,0 +1,44 @@
+ ['admin:r', 'assistenz:r']
+ )
+ );
+
+ // Loads WidgetLib
+ //$this->load->library('WidgetLib');
+
+ // Loads phrases system
+ $this->loadPhrases(
+ array(
+ 'global',
+ 'ui'
+ //'ferien'
+ )
+ );
+ }
+
+ // -----------------------------------------------------------------------------------------------------------------
+ // Public methods
+
+ /**
+ * Everything has a beginning
+ */
+ public function index()
+ {
+ $this->load->view('lehre/ferienverwaltung.php');
+ }
+}
diff --git a/application/models/organisation/Ferien_model.php b/application/models/organisation/Ferien_model.php
index 2ade75aa4..d1af53f21 100644
--- a/application/models/organisation/Ferien_model.php
+++ b/application/models/organisation/Ferien_model.php
@@ -9,6 +9,6 @@ class Ferien_model extends DB_Model
{
parent::__construct();
$this->dbTable = 'lehre.tbl_ferien';
- $this->pk = array('studiengang_kz', 'bezeichnung');
+ $this->pk = 'ferien_id';
}
}
diff --git a/application/views/lehre/ferienverwaltung.php b/application/views/lehre/ferienverwaltung.php
new file mode 100644
index 000000000..3a9a615ca
--- /dev/null
+++ b/application/views/lehre/ferienverwaltung.php
@@ -0,0 +1,29 @@
+ 'Ferienverwaltung',
+ 'axios027' => true,
+ 'bootstrap5' => true,
+ 'fontawesome6' => true,
+ 'vue3' => true,
+ 'filtercomponent' => true,
+ 'navigationcomponent' => true,
+ 'tabulator6' => true,
+ 'primevue3' => true,
+ 'vuedatepicker11' => true,
+ 'customJSModules' => array('public/js/apps/lehre/Ferienverwaltung/Ferienverwaltung.js'),
+ );
+
+ $this->load->view('templates/FHC-Header', $includesArray);
+?>
+
+
+
+load->view('templates/FHC-Footer', $includesArray); ?>
+
diff --git a/public/js/api/factory/ferienverwaltung/ferienverwaltung.js b/public/js/api/factory/ferienverwaltung/ferienverwaltung.js
new file mode 100644
index 000000000..093e62255
--- /dev/null
+++ b/public/js/api/factory/ferienverwaltung/ferienverwaltung.js
@@ -0,0 +1,48 @@
+/**
+ * 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 {
+ getFerien(studiengang_kz) {
+ return {
+ method: 'get',
+ url: 'api/frontend/v1/education/ferien/getFerien',
+ params: {
+ studiengang_kz
+ }
+ };
+ },
+ getStg() {
+ return {
+ method: 'get',
+ url: 'api/frontend/v1/education/ferien/getStg'
+ };
+ },
+ insert(params) {
+ return {
+ method: 'post',
+ url: 'api/frontend/v1/education/ferien/insert',
+ params
+ };
+ },
+ delete(ferien_id) {
+ return {
+ method: 'post',
+ url: 'api/frontend/v1/education/ferien/delete',
+ params: { ferien_id }
+ };
+ }
+};
\ No newline at end of file
diff --git a/public/js/apps/lehre/Ferienverwaltung/Ferienverwaltung.js b/public/js/apps/lehre/Ferienverwaltung/Ferienverwaltung.js
new file mode 100644
index 000000000..ad7106366
--- /dev/null
+++ b/public/js/apps/lehre/Ferienverwaltung/Ferienverwaltung.js
@@ -0,0 +1,12 @@
+import Ferienverwaltung from '../../../components/Ferienverwaltung/Ferienverwaltung.js';
+import PluginsPhrasen from '../../../plugins/Phrasen.js';
+
+const app = Vue.createApp({
+ name: 'FerienverwaltungApp',
+ components: {
+ Ferienverwaltung
+ }
+});
+app
+ .use(PluginsPhrasen)
+ .mount('#main');
\ No newline at end of file
diff --git a/public/js/components/Ferienverwaltung/Ferienverwaltung.js b/public/js/components/Ferienverwaltung/Ferienverwaltung.js
new file mode 100644
index 000000000..d1f6bc5b6
--- /dev/null
+++ b/public/js/components/Ferienverwaltung/Ferienverwaltung.js
@@ -0,0 +1,213 @@
+import {CoreFilterCmpt} from "../filter/Filter.js";
+import FormInput from "../Form/Input.js";
+import FerienNew from "./New.js";
+//import KontoEdit from "./Konto/Edit.js";
+
+import ApiFerienverwaltung from '../../api/factory/ferienverwaltung/ferienverwaltung.js';
+
+export default {
+ name: "Ferienverwaltung",
+ components: {
+ CoreFilterCmpt,
+ FormInput,
+ FerienNew
+ //KontoEdit
+ },
+ props: {
+ //modelValue: Object,
+ //~ config: {
+ //~ type: Object,
+ //~ default: {}
+ //~ }
+ },
+ data() {
+ return {
+ studiengang_kz_list: [],
+ studiengang_kz: null,
+ loading: false,
+ tabulatorOptions: {
+ ajaxURL: 'dummy',
+ ajaxRequestFunc: () => this.$api.call(
+ ApiFerienverwaltung.getFerien(this.studiengang_kz)
+ ),
+ ajaxResponse: (url, params, response) => response.data,
+ columns: [
+ {title:"Ferien Id", field:"ferien_id", visible: false},
+ {title:"Studiengang", field:"studiengang_kuerzel"},
+ {
+ title:"Datum von",
+ field:"vondatum",
+ formatter: function (cell) {
+ const dateStr = cell.getValue();
+ if (!dateStr) return "";
+
+ const date = new Date(dateStr);
+ return date.toLocaleString("de-DE", {
+ day: "2-digit",
+ month: "2-digit",
+ year: "numeric",
+ hour12: false
+ });
+ }
+ },
+ {
+ title:"Datum bis",
+ field:"bisdatum",
+ formatter: function (cell) {
+ const dateStr = cell.getValue();
+ if (!dateStr) return "";
+
+ const date = new Date(dateStr);
+ return date.toLocaleString("de-DE", {
+ day: "2-digit",
+ month: "2-digit",
+ year: "numeric",
+ hour12: false
+ });
+ }
+ },
+ {title:"Bezeichnung", field:"bezeichnung"},
+ {title:"Aktionen", field: "actions",
+ minWidth: 150, // Ensures Action-buttons will be always fully displayed
+ formatter: (cell, formatterParams, onRendered) => {
+ let container = document.createElement('div');
+ container.className = "d-flex gap-2";
+
+ //~ let button = document.createElement('button');
+ //~ button.className = 'btn btn-outline-secondary btn-action';
+ //~ button.innerHTML = '';
+ //~ button.title = this.$p.t('person', 'adresse_edit');
+ //~ button.addEventListener('click', (event) =>
+ //~ this.actionEditAdress(cell.getData().adresse_id)
+ //~ );
+ //~ container.append(button);
+ let button = document.createElement('button');
+ button.className = 'btn btn-outline-secondary';
+ button.innerHTML = '';
+ button.addEventListener('click', evt => {
+ evt.stopPropagation();
+ this.$fhcAlert
+ .confirmDelete()
+ .then(result => result ? cell.getData().ferien_id : Promise.reject({handled:true}))
+ .then(ferien_id => this.$api.call(ApiFerienverwaltung.delete(ferien_id)))
+ .then(() => {
+ //cell.getRow().delete();
+ this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successDelete'));
+ this.reload();
+ })
+ .catch(this.$fhcAlert.handleSystemError);
+ });
+ container.append(button);
+
+ return container;
+ },
+ frozen: true
+ }
+ ]
+ },
+ tabulatorEvents: [
+ {
+ event: 'tableBuilt',
+ handler: async () => {
+
+ //await this.$p.loadCategory(['ferien', 'ui']);
+ await this.$p.loadCategory(['global', 'ferien']);
+
+ let cm = this.$refs.table.tabulator.columnManager;
+
+ cm.getColumnByField('ferien_id').component.updateDefinition({
+ title: this.$p.t('ferien', 'ferien_id'),
+ });
+ cm.getColumnByField('studiengang_kuerzel').component.updateDefinition({
+ title: this.$p.t('ferien', 'studiengang_kuerzel'),
+ });
+ cm.getColumnByField('vondatum').component.updateDefinition({
+ title: this.$p.t('ferien', 'vondatum'),
+ });
+ cm.getColumnByField('bisdatum').component.updateDefinition({
+ title: this.$p.t('ferien', 'bisdatum'),
+ });
+ cm.getColumnByField('bezeichnung').component.updateDefinition({
+ title: this.$p.t('global', 'bezeichnung'),
+ });
+ cm.getColumnByField('actions').component.updateDefinition({
+ title: this.$p.t('global', 'aktionen')
+ });
+ }
+ }
+ ]
+ }
+ },
+ computed: {
+ },
+ methods: {
+ reload() {
+ this.$refs.table.reloadTable();
+ },
+ updateData(data) {
+ if (!data)
+ return this.reload();
+ //this.$refs.table.tabulator.updateOrAddData(data);
+ },
+ actionNew() {
+ this.$refs.new.open();
+ },
+ loadByStg() {
+ this.reload();
+ }
+ },
+ created() {
+ this.loading = true;
+ this.$api
+ .call(ApiFerienverwaltung.getStg())
+ .then(result => {
+ this.studiengang_kz_list = result.data;
+ this.loading = false;
+ }
+ )
+ .catch(error => {
+ if (error)
+ this.$fhcAlert.handleSystemError(error);
+ this.loading = false;
+ });
+ },
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
`
+};
\ No newline at end of file
diff --git a/public/js/components/Ferienverwaltung/New.js b/public/js/components/Ferienverwaltung/New.js
new file mode 100644
index 000000000..dad8a4034
--- /dev/null
+++ b/public/js/components/Ferienverwaltung/New.js
@@ -0,0 +1,120 @@
+import BsModal from "../Bootstrap/Modal.js";
+import BsConfirm from "../Bootstrap/Confirm.js";
+import CoreForm from "../Form/Form.js";
+import FormValidation from "../Form/Validation.js";
+import FormInput from "../Form/Input.js";
+
+import ApiFerien from '../../api/factory/ferienverwaltung/ferienverwaltung.js';
+
+export default {
+ components: {
+ BsModal,
+ CoreForm,
+ FormValidation,
+ FormInput
+ },
+ props: {
+ studiengang_kz_list: {
+ type: Array,
+ required: true
+ }
+ },
+ data() {
+ return {
+ loading: false,
+ data: {},
+ };
+ },
+ computed: {
+ },
+ methods: {
+ save() {
+ this.$refs.form.clearValidation();
+ this.loading = true;
+
+ this.$refs.form
+ .call(ApiFerien.insert(this.data))
+ .then(result => {
+ this.$emit('saved', result.data);
+ this.loading = false;
+ this.$refs.modal.hide();
+ this.$fhcAlert.alertSuccess(this.$p.t('ui/gespeichert'));
+ })
+ .catch(error => {
+ if (error)
+ this.$fhcAlert.handleSystemError(error);
+ this.loading = false;
+ });
+ },
+ open() {
+ this.data = {
+ studiengang_kz: null,
+ bezeichnung: '',
+ vondatum: null,
+ bisdatum: null
+ };
+ this.$refs.modal.show();
+ },
+ preventCloseOnLoading(ev) {
+ if (this.loading)
+ ev.returnValue = false;
+ }
+ },
+ template: `
+
+
+
+
+
+
+
+
+
+
+ `
+};
\ No newline at end of file