mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-07-18 23:42:17 +00:00
tempus pre-alpha version
This commit is contained in:
@@ -1,171 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2024 fhcomplete.org
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
if (! defined('BASEPATH')) exit('No direct script access allowed');
|
||||
|
||||
class Kalender extends FHCAPI_Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* Object initialization
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
parent::__construct([
|
||||
'getRoomplan' => self::PERM_LOGGED,
|
||||
'Stunden' => self::PERM_LOGGED,
|
||||
'Reservierungen' => self::PERM_LOGGED,
|
||||
'getStundenplan' => self::PERM_LOGGED,
|
||||
'getLehreinheitStudiensemester' => self::PERM_LOGGED,
|
||||
'updateKalenderEvent' => 'lehre/lvplan:rw',
|
||||
'addKalenderEvent' => 'lehre/lvplan:rw'
|
||||
]);
|
||||
|
||||
$this->load->library('LogLib');
|
||||
$this->loglib->setConfigs(array(
|
||||
'classIndex' => 5,
|
||||
'functionIndex' => 5,
|
||||
'lineIndex' => 4,
|
||||
'dbLogType' => 'API', // required
|
||||
'dbExecuteUser' => 'RESTful API'
|
||||
));
|
||||
|
||||
$this->uid = getAuthUID();
|
||||
|
||||
$this->load->library('form_validation');
|
||||
|
||||
//load models
|
||||
//$this->load->model('ressource/Stundenplan_model', 'StundenplanModel');
|
||||
//$this->load->model('ressource/Reservierung_model', 'ReservierungModel');
|
||||
|
||||
$this->load->library('KalenderLib');
|
||||
|
||||
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------
|
||||
// Public methods
|
||||
|
||||
/**
|
||||
* fetches Stunden layout from database
|
||||
* @access public
|
||||
*
|
||||
*/
|
||||
public function Stunden()
|
||||
{
|
||||
$this->load->model('ressource/Stunde_model', 'StundeModel');
|
||||
|
||||
$stunden = $this->StundeModel->load();
|
||||
|
||||
$stunden = $this->getDataOrTerminateWithError($stunden);
|
||||
|
||||
$this->terminateWithSuccess($stunden);
|
||||
}
|
||||
|
||||
/**
|
||||
* fetches stundenplan events from a Room and start/end date
|
||||
* @access public
|
||||
*
|
||||
*/
|
||||
public function getRoomplan()
|
||||
{
|
||||
|
||||
// form validation
|
||||
$this->load->library('form_validation');
|
||||
$this->form_validation->set_data($_GET);
|
||||
$this->form_validation->set_rules('start_date',"start_date","required");
|
||||
$this->form_validation->set_rules('end_date',"end_date","required");
|
||||
$this->form_validation->set_rules('ort_kurzbz',"ort_kurzbz","required");
|
||||
if($this->form_validation->run() === FALSE) $this->terminateWithValidationErrors($this->form_validation->error_array());
|
||||
|
||||
// storing the get parameter in local variables
|
||||
$start_date = $this->input->get('start_date', TRUE);
|
||||
$end_date = $this->input->get('end_date', TRUE);
|
||||
$ort_kurzbz = $this->input->get('ort_kurzbz', TRUE);
|
||||
|
||||
$stundenplan_data =$this->kalenderlib->getRoomData($ort_kurzbz, $start_date, $end_date);
|
||||
|
||||
$this->terminateWithSuccess($stundenplan_data);
|
||||
}
|
||||
|
||||
public function updateKalenderEvent()
|
||||
{
|
||||
// form validation
|
||||
$this->load->library('form_validation');
|
||||
$this->form_validation->set_data($_POST);
|
||||
$this->form_validation->set_rules('kalender_id',"kalender_id","required");
|
||||
$this->form_validation->set_rules('ort_kurzbz',"ort_kurzbz","required");
|
||||
$this->form_validation->set_rules('start_date',"start_date","required");
|
||||
$this->form_validation->set_rules('end_date',"end_date","optional");
|
||||
|
||||
if($this->form_validation->run() === FALSE) $this->terminateWithValidationErrors($this->form_validation->error_array());
|
||||
|
||||
// storing the get parameter in local variables
|
||||
$kalender_id = $this->input->post('kalender_id', TRUE);
|
||||
$ort_kurzbz = $this->input->post('ort_kurzbz', TRUE);
|
||||
$start_date = $this->input->post('start_date', TRUE);
|
||||
$end_date = $this->input->post('end_date', TRUE);
|
||||
|
||||
|
||||
// Was passiert hier?
|
||||
// Raumänderung, Tagesänderung, Start / Ende Zeit korrektur
|
||||
// Ist das alles ein Endpunkt?
|
||||
$stundenplan_data =$this->kalenderlib->updateKalenderEvent($this->uid,$kalender_id, $ort_kurzbz, $start_date, $end_date);
|
||||
|
||||
$this->terminateWithSuccess($stundenplan_data);
|
||||
}
|
||||
|
||||
public function addKalenderEvent()
|
||||
{
|
||||
// form validation
|
||||
$this->load->library('form_validation');
|
||||
$this->form_validation->set_data($_POST);
|
||||
$this->form_validation->set_rules('lehreinheit_id',"kalender_id","required");
|
||||
$this->form_validation->set_rules('ort_kurzbz',"ort_kurzbz","required");
|
||||
$this->form_validation->set_rules('start_date',"start_date","required");
|
||||
$this->form_validation->set_rules('end_date',"end_date","required");
|
||||
|
||||
if($this->form_validation->run() === FALSE) $this->terminateWithValidationErrors($this->form_validation->error_array());
|
||||
|
||||
// storing the get parameter in local variables
|
||||
$lehreinheit_id = $this->input->post('lehreinheit_id', TRUE);
|
||||
$ort_kurzbz = $this->input->post('ort_kurzbz', TRUE);
|
||||
$start_date = $this->input->post('start_date', TRUE);
|
||||
$end_date = $this->input->post('end_date', TRUE);
|
||||
|
||||
$this->kalenderlib->addKalenderEvent($this->uid, $ort_kurzbz, $start_date, $end_date, $lehreinheit_id);
|
||||
|
||||
$this->terminateWithSuccess();
|
||||
}
|
||||
|
||||
// gets the reservierungen of a room if the ort_kurzbz parameter is supplied otherwise gets the reservierungen of the stundenplan of a student
|
||||
public function Reservierungen($ort_kurzbz = null)
|
||||
{
|
||||
$this->terminateWithSuccess();
|
||||
}
|
||||
|
||||
public function getLehreinheitStudiensemester($lehreinheit_id)
|
||||
{
|
||||
$this->load->model('education/Lehreinheit_model', 'LehreinheitModel');
|
||||
$this->LehreinheitModel->addSelect(["studiensemester_kurzbz"]);
|
||||
$result = $this->LehreinheitModel->load($lehreinheit_id);
|
||||
$result = current($this->getDataOrTerminateWithError($result))->studiensemester_kurzbz;
|
||||
$this->terminateWithSuccess($result);
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2024 fhcomplete.org
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
if (! defined('BASEPATH')) exit('No direct script access allowed');
|
||||
|
||||
class Tempus extends FHCAPI_Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* Object initialization
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
parent::__construct([
|
||||
'getCourses' => self::PERM_LOGGED,
|
||||
]);
|
||||
|
||||
$this->load->library('LogLib');
|
||||
$this->loglib->setConfigs(array(
|
||||
'classIndex' => 5,
|
||||
'functionIndex' => 5,
|
||||
'lineIndex' => 4,
|
||||
'dbLogType' => 'API', // required
|
||||
'dbExecuteUser' => 'RESTful API'
|
||||
));
|
||||
|
||||
$this->load->library('form_validation');
|
||||
|
||||
//load models
|
||||
//$this->load->model('ressource/Stundenplan_model', 'StundenplanModel');
|
||||
//$this->load->model('ressource/Reservierung_model', 'ReservierungModel');
|
||||
|
||||
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------
|
||||
// Public methods
|
||||
|
||||
|
||||
/**
|
||||
* fetches courses
|
||||
* @access public
|
||||
*
|
||||
*/
|
||||
public function getCourses()
|
||||
{
|
||||
// form validation
|
||||
$this->load->library('form_validation');
|
||||
$this->form_validation->set_data($_GET);
|
||||
$this->form_validation->set_rules('searchfilter',"searchfilter","required");
|
||||
if($this->form_validation->run() === FALSE) $this->terminateWithValidationErrors($this->form_validation->error_array());
|
||||
|
||||
// storing the get parameter in local variables
|
||||
$searchfilter = $this->input->get('searchfiler', TRUE);
|
||||
|
||||
// TODO implement Loading Data
|
||||
$course_data = array(
|
||||
array(
|
||||
'lehreinheit_id'=>'1',
|
||||
'bezeichnung' => 'Englisch 1',
|
||||
'studiengang_kurzbz' => 'BMR',
|
||||
'semester' => '1',
|
||||
'kurzbz' => 'ENG',
|
||||
'lektoren' => array('OesterAn','KindlMa')
|
||||
),
|
||||
array(
|
||||
'lehreinheit_id'=>'2',
|
||||
'bezeichnung' => 'Mahtematik 1',
|
||||
'studiengang_kurzbz' => 'BMR',
|
||||
'semester' => '1',
|
||||
'kurzbz' => 'MAT',
|
||||
'lektoren' => array('BamberHa')
|
||||
)
|
||||
);
|
||||
|
||||
$this->terminateWithSuccess($course_data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
if (!defined('BASEPATH'))
|
||||
exit('No direct script access allowed');
|
||||
|
||||
|
||||
class Config extends FHCAPI_Controller
|
||||
{
|
||||
private $_ci;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct([
|
||||
'get' => ['admin:r', 'assistenz:r'],
|
||||
'getHeader' => ['admin:r', 'assistenz:r'],
|
||||
'set' => ['admin:r', 'assistenz:r'],
|
||||
]);
|
||||
|
||||
// Load Phrases
|
||||
$this->loadPhrases([
|
||||
'global',
|
||||
]);
|
||||
|
||||
$this->_ci = &get_instance();
|
||||
$this->_ci->load->model('ressource/Kalenderstatus_model', 'KalenderStatusModel');
|
||||
}
|
||||
|
||||
public function get()
|
||||
{
|
||||
$visible_status = $this->_ci->KalenderStatusModel->load();
|
||||
|
||||
$visible_status = getData($visible_status);
|
||||
|
||||
$config['visible_status'] = [
|
||||
"type" => "select",
|
||||
"label" => $this->p->t('ui', 'status'),
|
||||
"multiple" => true,
|
||||
"value" => 'all'
|
||||
];
|
||||
foreach ($visible_status as $status)
|
||||
{
|
||||
$config['visible_status']['options'][$status->status_kurzbz] = $status->status_kurzbz;
|
||||
}
|
||||
|
||||
$this->terminateWithSuccess($config);
|
||||
}
|
||||
public function getHeader()
|
||||
{
|
||||
$visible_status = $this->_ci->KalenderStatusModel->load();
|
||||
|
||||
$visible_status = getData($visible_status);
|
||||
|
||||
|
||||
$config['visible_status']['all'] = 'all';
|
||||
|
||||
foreach ($visible_status as $status)
|
||||
{
|
||||
$config['visible_status'][$status->status_kurzbz] = $status->bezeichnung;
|
||||
}
|
||||
|
||||
$this->terminateWithSuccess($config);
|
||||
}
|
||||
|
||||
public function set()
|
||||
{
|
||||
|
||||
$this->terminateWithSuccess();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
if (! defined('BASEPATH')) exit('No direct script access allowed');
|
||||
|
||||
class Coursepicker extends FHCAPI_Controller
|
||||
{
|
||||
private $_ci;
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct([
|
||||
'search' => self::PERM_LOGGED,
|
||||
]);
|
||||
|
||||
$this->_ci = &get_instance();
|
||||
$this->load->library('form_validation');
|
||||
|
||||
$this->_ci->load->model('education/lehreinheit_model', 'LehreinheitModel');
|
||||
$this->_ci->load->model('education/Lehreinheitmitarbeiter_model', 'LehreinheitmitarbeiterModel');
|
||||
|
||||
|
||||
$this->loadPhrases([
|
||||
'ui'
|
||||
]);
|
||||
}
|
||||
|
||||
public function search()
|
||||
{
|
||||
$query = $this->input->get('query');
|
||||
if (is_null($query))
|
||||
$this->terminateWithError($this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL);
|
||||
|
||||
$query_words = explode(' ', $query);
|
||||
|
||||
//TODO Where weiter anpassen z.B. Fachbereich
|
||||
$this->_ci->LehreinheitModel->addSelect('tbl_lehreinheit.lehreinheit_id,
|
||||
tbl_lehreinheit.unr,
|
||||
tbl_lehreinheit.lvnr,
|
||||
tbl_lehreinheit.lehrfach_id,
|
||||
lehrfach.kurzbz AS lehrfach,
|
||||
lehrfach.bezeichnung AS lehrfach_bez,
|
||||
lehrfach.farbe AS lehrfach_farbe,
|
||||
tbl_lehreinheit.lehrform_kurzbz AS lehrform,
|
||||
lema.mitarbeiter_uid AS lektor_uid,
|
||||
tbl_mitarbeiter.kurzbz AS lektor,
|
||||
tbl_studiengang.studiengang_kz,
|
||||
upper(tbl_studiengang.typ::character varying::text || tbl_studiengang.kurzbz::text) AS studiengang,
|
||||
lvb.semester,
|
||||
lvb.verband,
|
||||
lvb.gruppe,
|
||||
lvb.gruppe_kurzbz,
|
||||
tbl_lehreinheit.raumtyp,
|
||||
tbl_lehreinheit.raumtypalternativ,
|
||||
tbl_lehreinheit.stundenblockung,
|
||||
tbl_lehreinheit.wochenrythmus,
|
||||
lema.semesterstunden,
|
||||
lema.planstunden,
|
||||
tbl_lehreinheit.start_kw,
|
||||
tbl_lehreinheit.anmerkung,
|
||||
tbl_lehreinheit.studiensemester_kurzbz');
|
||||
|
||||
$this->_ci->LehreinheitModel->addJoin('lehre.tbl_lehreinheitmitarbeiter lema', 'tbl_lehreinheit.lehreinheit_id = lema.lehreinheit_id');
|
||||
$this->_ci->LehreinheitModel->addJoin('lehre.tbl_lehreinheitgruppe lvb', 'tbl_lehreinheit.lehreinheit_id = lvb.lehreinheit_id');
|
||||
$this->_ci->LehreinheitModel->addJoin('public.tbl_studiengang', 'lvb.studiengang_kz = tbl_studiengang.studiengang_kz');
|
||||
$this->_ci->LehreinheitModel->addJoin('lehre.tbl_lehrveranstaltung lehrfach', 'tbl_lehreinheit.lehrfach_id = lehrfach.lehrveranstaltung_id');
|
||||
$this->_ci->LehreinheitModel->addJoin('public.tbl_mitarbeiter', 'lema.mitarbeiter_uid = tbl_mitarbeiter.mitarbeiter_uid');
|
||||
$this->_ci->LehreinheitModel->addJoin('lehre.tbl_lehrform', 'tbl_lehrform.lehrform_kurzbz = tbl_lehreinheit.lehrform_kurzbz');
|
||||
|
||||
$this->_ci->MitarbeiterModel->db->group_start();
|
||||
|
||||
foreach ($query_words as $word)
|
||||
{
|
||||
$this->_ci->LehreinheitModel->db->group_start();
|
||||
$this->_ci->LehreinheitModel->db->where('lema.mitarbeiter_uid ILIKE', "%" . $word . "%");
|
||||
$this->_ci->LehreinheitModel->db->or_where('lvb.gruppe_kurzbz ILIKE', "%" . $word . "%");
|
||||
$this->_ci->LehreinheitModel->db->or_where('tbl_studiengang.kurzbzlang ILIKE', "%" . $word . "%");
|
||||
$this->_ci->LehreinheitModel->db->or_where('lvb.verband ILIKE', "%" . $word . "%");
|
||||
$this->_ci->LehreinheitModel->db->or_where('lvb.gruppe ILIKE', "%" . $word . "%");
|
||||
$this->_ci->LehreinheitModel->db->or_where('lehrfach.bezeichnung ILIKE', "%" . $word . "%");
|
||||
|
||||
if (is_numeric($word))
|
||||
{
|
||||
$this->_ci->LehreinheitModel->db->or_where('tbl_studiengang.studiengang_kz', $word);
|
||||
$this->_ci->LehreinheitModel->db->or_where('lvb.semester', $word);
|
||||
}
|
||||
$this->_ci->LehreinheitModel->db->group_end();
|
||||
|
||||
}
|
||||
$this->_ci->LehreinheitModel->db->group_end();
|
||||
$this->_ci->LehreinheitModel->db->where('tbl_lehreinheit.studiensemester_kurzbz = \'SS2025\'');
|
||||
$this->_ci->LehreinheitModel->db->where(array('tbl_lehrform.verplanen' => true));
|
||||
|
||||
$result = $this->_ci->LehreinheitModel->load();
|
||||
|
||||
$this->terminateWithSuccess(hasData($result) ? getData($result) : array());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
<?php
|
||||
if (! defined('BASEPATH')) exit('No direct script access allowed');
|
||||
|
||||
class Kalender extends FHCAPI_Controller
|
||||
{
|
||||
|
||||
private $_ci;
|
||||
const ALLOWED_PLAN_FILTER = ['ort', 'uid', 'stg'];
|
||||
const ALLOWED_ROOM_FILTER = ['lehreinheit_id', 'kalender_id'];
|
||||
|
||||
const ALLOWED_TO_UPDATE = ['start_time', 'end_time', 'ort_kurzbz'];
|
||||
/**
|
||||
* Object initialization
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
parent::__construct([
|
||||
'getStunden' => self::PERM_LOGGED,
|
||||
'getPlan' => self::PERM_LOGGED,
|
||||
'getPlanNew' => self::PERM_LOGGED,
|
||||
'getPlanByOrt' => self::PERM_LOGGED,
|
||||
'getRaumvorschlag' => self::PERM_LOGGED,
|
||||
'getZeitwuensche' => self::PERM_LOGGED,
|
||||
'getZeitsperren' => self::PERM_LOGGED,
|
||||
'updateKalenderEvent' => 'lehre/lvplan:rw',
|
||||
'addKalenderEvent' => 'lehre/lvplan:rw'
|
||||
]);
|
||||
|
||||
$this->_ci =& get_instance();
|
||||
|
||||
$this->_ci->load->library('LogLib');
|
||||
$this->_ci->load->library('form_validation');
|
||||
$this->_ci->load->library('KalenderLib');
|
||||
$this->loadPhrases([
|
||||
'ui'
|
||||
]);
|
||||
|
||||
|
||||
$this->_ci->loglib->setConfigs(array(
|
||||
'classIndex' => 5,
|
||||
'functionIndex' => 5,
|
||||
'lineIndex' => 4,
|
||||
'dbLogType' => 'API', // required
|
||||
'dbExecuteUser' => 'RESTful API'
|
||||
));
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------
|
||||
// Public methods
|
||||
/**
|
||||
* fetches Stunden layout from database
|
||||
* @access public
|
||||
*
|
||||
*/
|
||||
public function getStunden()
|
||||
{
|
||||
$this->load->model('ressource/Stunde_model', 'StundeModel');
|
||||
|
||||
$this->_ci->StundeModel->addOrder('stunde', 'ASC');
|
||||
$stunden = $this->_ci->StundeModel->load();
|
||||
|
||||
$stunden = $this->getDataOrTerminateWithError($stunden);
|
||||
|
||||
$this->terminateWithSuccess($stunden);
|
||||
}
|
||||
public function getPlan()
|
||||
{
|
||||
$this->_ci->form_validation->set_data($_GET);
|
||||
$this->_ci->form_validation->set_rules('start_date',"start_date","required");
|
||||
$this->_ci->form_validation->set_rules('end_date',"end_date","required");
|
||||
|
||||
if($this->_ci->form_validation->run() === FALSE)
|
||||
$this->terminateWithValidationErrors($this->_ci->form_validation->error_array());
|
||||
|
||||
$start_date = $this->_ci->input->get('start_date', TRUE);
|
||||
$end_date = $this->_ci->input->get('end_date', TRUE);
|
||||
|
||||
$filter = $this->_checkFilter(self::ALLOWED_PLAN_FILTER);
|
||||
|
||||
$stundenplan_data = $this->_ci->kalenderlib->getPlan(
|
||||
$start_date,
|
||||
$end_date,
|
||||
isset($filter->ort) ? $filter->ort : null,
|
||||
isset($filter->uid) ? $filter->uid : null,
|
||||
isset($filter->stg) ? $filter->stg : null
|
||||
);
|
||||
|
||||
$this->terminateWithSuccess($stundenplan_data);
|
||||
}
|
||||
|
||||
public function getPlanNew()
|
||||
{
|
||||
$this->_ci->form_validation->set_data($_GET);
|
||||
$this->_ci->form_validation->set_rules('start_date',"start_date","required");
|
||||
$this->_ci->form_validation->set_rules('end_date',"end_date","required");
|
||||
|
||||
if($this->_ci->form_validation->run() === FALSE)
|
||||
$this->terminateWithValidationErrors($this->_ci->form_validation->error_array());
|
||||
|
||||
$start_date = $this->_ci->input->get('start_date', TRUE);
|
||||
$end_date = $this->_ci->input->get('end_date', TRUE);
|
||||
|
||||
$filter = $this->_checkFilter(self::ALLOWED_PLAN_FILTER);
|
||||
|
||||
$stundenplan_data = $this->_ci->kalenderlib->getPlanNew(
|
||||
$start_date,
|
||||
$end_date,
|
||||
isset($filter->ort) ? $filter->ort : null,
|
||||
isset($filter->uid) ? $filter->uid : null,
|
||||
isset($filter->stg) ? $filter->stg : null
|
||||
);
|
||||
|
||||
$this->terminateWithSuccess($stundenplan_data);
|
||||
}
|
||||
|
||||
public function getPlanByOrt($start_date = null, $end_date = null, $ort = null)
|
||||
{
|
||||
if (!isset($start_date) || !isset($end_date) || !isset($ort))
|
||||
{
|
||||
$this->_ci->form_validation->set_data($_GET);
|
||||
$this->_ci->form_validation->set_rules('start_date',"start_date","required");
|
||||
$this->_ci->form_validation->set_rules('end_date',"end_date","required");
|
||||
$this->_ci->form_validation->set_rules('ort',"ort","required");
|
||||
if($this->_ci->form_validation->run() === FALSE)
|
||||
$this->terminateWithValidationErrors($this->_ci->form_validation->error_array());
|
||||
|
||||
$start_date = $this->_ci->input->get('start_date', TRUE);
|
||||
$end_date = $this->_ci->input->get('end_date', TRUE);
|
||||
$ort = $this->_ci->input->get('ort', TRUE);
|
||||
}
|
||||
|
||||
$this->terminateWithSuccess($this->_ci->kalenderlib->getPlanByOrt($start_date, $end_date, $ort));
|
||||
}
|
||||
|
||||
public function getZeitsperren()
|
||||
{
|
||||
$this->_ci->form_validation->set_data($_GET);
|
||||
$this->_ci->form_validation->set_rules('start_date',"start_date","required");
|
||||
$this->_ci->form_validation->set_rules('end_date',"end_date","required");
|
||||
$this->_ci->form_validation->set_rules('emp',"emp","required");
|
||||
if($this->_ci->form_validation->run() === FALSE)
|
||||
$this->terminateWithValidationErrors($this->_ci->form_validation->error_array());
|
||||
|
||||
$start_date = $this->_ci->input->get('start_date', TRUE);
|
||||
$end_date = $this->_ci->input->get('end_date', TRUE);
|
||||
$emp = $this->_ci->input->get('emp', TRUE);
|
||||
|
||||
$stundenplan_data = $this->_ci->kalenderlib->getZeitsperren($start_date, $end_date, $emp);
|
||||
|
||||
$this->terminateWithSuccess($stundenplan_data);
|
||||
}
|
||||
|
||||
public function getZeitwuensche()
|
||||
{
|
||||
$this->_ci->form_validation->set_data($_GET);
|
||||
$this->_ci->form_validation->set_rules('start_date',"start_date","required");
|
||||
$this->_ci->form_validation->set_rules('end_date',"end_date","required");
|
||||
$this->_ci->form_validation->set_rules('emp',"emp","required");
|
||||
|
||||
if($this->_ci->form_validation->run() === FALSE)
|
||||
$this->terminateWithValidationErrors($this->_ci->form_validation->error_array());
|
||||
|
||||
$start_date = $this->_ci->input->get('start_date', TRUE);
|
||||
$end_date = $this->_ci->input->get('end_date', TRUE);
|
||||
$emp = $this->_ci->input->get('emp', TRUE);
|
||||
|
||||
$stundenplan_data = $this->_ci->kalenderlib->getZeitwuensche($start_date, $end_date, $emp);
|
||||
|
||||
$this->terminateWithSuccess($stundenplan_data);
|
||||
}
|
||||
|
||||
public function updateKalenderEvent()
|
||||
{
|
||||
$this->_ci->form_validation->set_data($_POST);
|
||||
$this->_ci->form_validation->set_rules('kalender_id',"kalender_id","required");
|
||||
|
||||
if($this->_ci->form_validation->run() === FALSE)
|
||||
$this->terminateWithValidationErrors($this->_ci->form_validation->error_array());
|
||||
|
||||
$updateFields = $this->_checkUpdate($this->_ci->input->post('updatedInfos', TRUE));
|
||||
$kalender_id = $this->_ci->input->post('kalender_id', TRUE);
|
||||
|
||||
if (isset($updateFields->ort_kurzbz) && !isEmptyString($updateFields->ort_kurzbz))
|
||||
{
|
||||
$result = $this->_ci->kalenderlib->updateOrt($kalender_id, $updateFields->ort_kurzbz);
|
||||
|
||||
if (isError($result))
|
||||
$this->terminateWithError(getError($result));
|
||||
}
|
||||
|
||||
if (isset($updateFields->start_time) || isset($updateFields->end_time))
|
||||
{
|
||||
$result = $this->_ci->kalenderlib->updateZeit($kalender_id, $updateFields->start_time, $updateFields->end_time);
|
||||
|
||||
if (isError($result))
|
||||
$this->terminateWithError(getError($result));
|
||||
}
|
||||
|
||||
$this->terminateWithSuccess('Erfolgreich');
|
||||
}
|
||||
|
||||
public function getRaumvorschlag()
|
||||
{
|
||||
$this->_ci->form_validation->set_data($_GET);
|
||||
$this->_ci->form_validation->set_rules('start_date',"start_date","required");
|
||||
$this->_ci->form_validation->set_rules('end_date',"end_date","required");
|
||||
|
||||
if($this->_ci->form_validation->run() === FALSE)
|
||||
$this->terminateWithValidationErrors($this->_ci->form_validation->error_array());
|
||||
|
||||
$start_date = $this->_ci->input->get('start_date', TRUE);
|
||||
$end_date = $this->_ci->input->get('end_date', TRUE);
|
||||
|
||||
$filter = $this->_checkFilter(self::ALLOWED_ROOM_FILTER);
|
||||
|
||||
if (isset($filter->lehreinheit_id))
|
||||
{
|
||||
$result = $this->_ci->kalenderlib->getRaumvorschlagByLehreinheitID(
|
||||
$start_date,
|
||||
$end_date,
|
||||
$filter->lehreinheit_id
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($filter->kalender_id))
|
||||
{
|
||||
$result = $this->_ci->kalenderlib->getRaumvorschlagByKalenderID(
|
||||
$start_date,
|
||||
$end_date,
|
||||
$filter->kalender_id
|
||||
);
|
||||
}
|
||||
|
||||
if (isError($result))
|
||||
$this->terminateWithError(getError($result));
|
||||
|
||||
$this->terminateWithSuccess(getData($result));
|
||||
}
|
||||
|
||||
public function addKalenderEvent()
|
||||
{
|
||||
$this->_ci->form_validation->set_data($_POST);
|
||||
$this->_ci->form_validation->set_rules('lehreinheit_id',"lehreinheit_id","required");
|
||||
$this->_ci->form_validation->set_rules('start_date',"start_date","required");
|
||||
$this->_ci->form_validation->set_rules('end_date',"end_date","required");
|
||||
|
||||
if($this->_ci->form_validation->run() === FALSE)
|
||||
$this->terminateWithValidationErrors($this->_ci->form_validation->error_array());
|
||||
|
||||
$lehreinheit_id = $this->_ci->input->post('lehreinheit_id', TRUE);
|
||||
$ort_kurzbz = $this->_ci->input->post('ort_kurzbz', TRUE);
|
||||
$start_date = $this->_ci->input->post('start_date', TRUE);
|
||||
$end_date = $this->_ci->input->post('end_date', TRUE);
|
||||
|
||||
|
||||
$result = $this->_ci->kalenderlib->addKalenderEvent($start_date, $end_date, $lehreinheit_id, $ort_kurzbz);
|
||||
|
||||
if (isError($result))
|
||||
$this->terminateWithError(getError($result));
|
||||
|
||||
$this->terminateWithSuccess('Erfolgreich');
|
||||
}
|
||||
|
||||
private function _checkFilter($filters)
|
||||
{
|
||||
$filter_valid = true;
|
||||
$filter_object = new stdClass();
|
||||
foreach ($filters as $filter)
|
||||
{
|
||||
if ($this->_ci->input->get($filter))
|
||||
{
|
||||
$filter_valid = true;
|
||||
$filter_object->$filter = $this->_ci->input->get($filter);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$filter_valid)
|
||||
$this->terminateWithError($this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL);
|
||||
|
||||
return $filter_object;
|
||||
}
|
||||
|
||||
private function _checkUpdate($updateInfos)
|
||||
{
|
||||
$update_valid = false;
|
||||
$update_object = new stdClass();
|
||||
foreach (self::ALLOWED_TO_UPDATE as $filter)
|
||||
{
|
||||
if (isset($updateInfos[$filter]))
|
||||
{
|
||||
$update_valid = true;
|
||||
$update_object->$filter = $updateInfos[$filter];
|
||||
}
|
||||
}
|
||||
|
||||
if (!$update_valid)
|
||||
$this->terminateWithError($this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL);
|
||||
|
||||
|
||||
return $update_object;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
if (! defined('BASEPATH')) exit('No direct script access allowed');
|
||||
|
||||
class MigrateKalender extends CLI_Controller
|
||||
class MigrateKalender extends Auth_Controller
|
||||
{
|
||||
|
||||
/**
|
||||
@@ -16,7 +16,7 @@ class MigrateKalender extends CLI_Controller
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
parent::__construct(array('index' => ['admin:rw']));
|
||||
|
||||
$this->load->model('ressource/Kalender_model', 'KalenderModel');
|
||||
$this->load->model('ressource/Kalender_Lehreinheit_model', 'KalenderLehreinheitModel');
|
||||
@@ -27,21 +27,65 @@ class MigrateKalender extends CLI_Controller
|
||||
/**
|
||||
* Everything has a beginning
|
||||
*/
|
||||
public function index()
|
||||
public function index($von = null, $bis = null)
|
||||
{
|
||||
$von = date('Y-m-d') // TODO
|
||||
$bis = date('Y-m-d') // TODO
|
||||
|
||||
$db = new DB_Model();
|
||||
$stpldevsql = '
|
||||
SELECT *,
|
||||
(SELECT beginn FROM lehre.tbl_stunde WHERE stunde=tbl_stundenplandev.stunde) as beginn,
|
||||
(SELECT ende FROM lehre.tbl_stunde WHERE stunde=tbl_stundenplandev.stunde) as ende
|
||||
FROM
|
||||
lehre.tbl_stundenplandev WHERE datum>=? and datum<=? ORDER BY datum, stunde, unr';
|
||||
|
||||
$stpldev = $db->execReadOnlyQuery($stpldevsql, array($von, $bis));
|
||||
if (hasData($stpldev))
|
||||
$stpldevsql = '
|
||||
WITH eindeutige_stunden AS (
|
||||
SELECT DISTINCT unr, datum, stunde
|
||||
FROM lehre.tbl_stundenplandev
|
||||
WHERE datum >= ? AND datum <= ?
|
||||
),
|
||||
block_keys AS (
|
||||
SELECT
|
||||
unr,
|
||||
datum,
|
||||
stunde,
|
||||
stunde - ROW_NUMBER() OVER (PARTITION BY unr, datum ORDER BY stunde) AS block_nr
|
||||
FROM eindeutige_stunden
|
||||
),
|
||||
blocks AS (
|
||||
SELECT
|
||||
bk.unr,
|
||||
bk.datum,
|
||||
bk.block_nr,
|
||||
MIN(bk.stunde) AS stunde_von,
|
||||
MAX(bk.stunde) AS stunde_bis,
|
||||
MIN(sp.lehreinheit_id) AS lehreinheit_id,
|
||||
MIN(sp.ort_kurzbz) AS ort_kurzbz,
|
||||
array_agg(sp.stundenplandev_id ORDER BY bk.stunde) AS stundenplandev_ids,
|
||||
MIN(sp.insertamum) AS insertamum,
|
||||
(array_agg(sp.insertvon ORDER BY sp.insertamum ASC))[1] AS insertvon,
|
||||
MAX(sp.updateamum) AS updateamum,
|
||||
(array_agg(sp.updatevon ORDER BY sp.updateamum DESC))[1] AS updatevon
|
||||
FROM block_keys bk JOIN lehre.tbl_stundenplandev sp ON sp.unr = bk.unr AND sp.datum = bk.datum AND sp.stunde = bk.stunde
|
||||
WHERE sp.datum >= ? AND sp.datum <= ?
|
||||
GROUP BY bk.unr, bk.datum, bk.block_nr
|
||||
)
|
||||
SELECT
|
||||
b.stundenplandev_ids,
|
||||
b.unr,
|
||||
b.datum,
|
||||
b.block_nr,
|
||||
b.lehreinheit_id,
|
||||
b.ort_kurzbz,
|
||||
b.datum + stundevon.beginn AS von,
|
||||
b.datum + stundebis.ende AS bis,
|
||||
b.insertamum,
|
||||
b.insertvon,
|
||||
b.updateamum,
|
||||
b.updatevon
|
||||
FROM blocks b
|
||||
JOIN lehre.tbl_stunde stundevon ON stundevon.stunde = b.stunde_von
|
||||
JOIN lehre.tbl_stunde stundebis ON stundebis.stunde = b.stunde_bis
|
||||
ORDER BY b.datum, b.unr, b.block_nr;';
|
||||
|
||||
$stpldev = $db->execReadOnlyQuery($stpldevsql, array($von, $bis, $von, $bis));
|
||||
|
||||
|
||||
if (hasData($stpldev))
|
||||
{
|
||||
// Pruefen ob der Eintrag schon in Sync Tabelle vorhanden ist
|
||||
// Wenn neuere Änderungen vorhanden dann Update
|
||||
@@ -51,12 +95,39 @@ class MigrateKalender extends CLI_Controller
|
||||
// in der neuen Tabelle auch archivieren oder loeschen
|
||||
|
||||
$data = getData($stpldev);
|
||||
foreach($data as $rowstpl)
|
||||
foreach($data as $block)
|
||||
{
|
||||
$SyncResult = $this->SyncModel->loadWhere(
|
||||
array('stundenplandev_id' => $rowstpl->stundenplandev_id)
|
||||
);
|
||||
if(hasData($SyncResult))
|
||||
$this->SyncModel->db->where_in('stundenplandev_id', $block->stundenplandev_ids);
|
||||
$sync_result = $this->SyncModel->load();
|
||||
|
||||
if (!hasData($sync_result))
|
||||
{
|
||||
$kalender_id = $this->_insertKalender($block);
|
||||
if ($kalender_id)
|
||||
{
|
||||
$this->_insertSync($block->stundenplandev_ids, $kalender_id);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$syncData = getData($sync_result);
|
||||
$kalender_id = $syncData[0]->kalender_id;
|
||||
$last_sync = $syncData[0]->lastupdate;
|
||||
$synced_ids = array_column($syncData, 'stundenplandev_id');
|
||||
|
||||
if ($block->updateamum > $last_sync)
|
||||
{
|
||||
$this->_updateKalender($kalender_id, $block);
|
||||
$this->_updateSync($synced_ids, $kalender_id);
|
||||
}
|
||||
|
||||
$fehlende = array_diff($block->stundenplandev_ids, $synced_ids);
|
||||
if (!empty($fehlende))
|
||||
{
|
||||
$this->_insertSync($fehlende, $kalender_id);
|
||||
}
|
||||
}
|
||||
/*if(hasData($SyncResult))
|
||||
{
|
||||
//bereits vorhanden
|
||||
// TODO Update
|
||||
@@ -115,8 +186,89 @@ class MigrateKalender extends CLI_Controller
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function _insertKalender($block)
|
||||
{
|
||||
$result = $this->KalenderModel->insert(
|
||||
array (
|
||||
'von' => $block->von,
|
||||
'bis' => $block->bis,
|
||||
'typ' => 'lehreinheit',
|
||||
'status_kurzbz'=> 'visible_student',
|
||||
'insertamum' => $block->insertamum,
|
||||
'insertvon' => $block->insertvon,
|
||||
'updateamum' => $block->updateamum,
|
||||
'updatevon' => $block->updatevon
|
||||
)
|
||||
);
|
||||
if(!isSuccess($result))
|
||||
return null;
|
||||
|
||||
$kalender_id = getData($result);
|
||||
|
||||
$this->KalenderLehreinheitModel->insert(
|
||||
array (
|
||||
'kalender_id' => $kalender_id,
|
||||
'lehreinheit_id'=> $block->lehreinheit_id
|
||||
)
|
||||
);
|
||||
$this->KalenderOrtModel->insert(
|
||||
array (
|
||||
'kalender_id' => $kalender_id,
|
||||
'ort_kurzbz' => $block->ort_kurzbz
|
||||
)
|
||||
);
|
||||
|
||||
return $kalender_id;
|
||||
}
|
||||
|
||||
private function _insertSync($ids, $kalender_id)
|
||||
{
|
||||
foreach($ids as $id)
|
||||
{
|
||||
$this->SyncModel->insert(
|
||||
array (
|
||||
'stundenplandev_id' => $id,
|
||||
'kalender_id' => $kalender_id,
|
||||
'lastupdate' => date('Y-m-d H:i:s')
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function _updateKalender($kalender_id, $block)
|
||||
{
|
||||
$this->KalenderModel->update(
|
||||
array (
|
||||
'kalender_id' => $kalender_id
|
||||
),
|
||||
array (
|
||||
'von' => $block->von,
|
||||
'bis' => $block->bis,
|
||||
'updateamum'=> $block->updateamum,
|
||||
'updatevon' => $block->updatevon
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private function _updateSync($ids, $kalender_id)
|
||||
{
|
||||
foreach($ids as $id)
|
||||
{
|
||||
$this->SyncModel->update(
|
||||
array (
|
||||
'stundenplandev_id' => $id,
|
||||
'kalender_id' => $kalender_id
|
||||
),
|
||||
array (
|
||||
'lastupdate' => date('Y-m-d H:i:s')
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,159 +4,480 @@ if (! defined("BASEPATH")) exit("No direct script access allowed");
|
||||
|
||||
class KalenderLib
|
||||
{
|
||||
private $_ci;
|
||||
/**
|
||||
* Loads model OrganisationseinheitModel
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->ci =& get_instance();
|
||||
$this->_ci =& get_instance();
|
||||
|
||||
$this->ci->load->model('ressource/Kalender_model', 'KalenderModel');
|
||||
$this->ci->load->model('ressource/Kalender_Lehreinheit_model', 'KalenderLehreinheitModel');
|
||||
$this->ci->load->model('ressource/Kalender_Ort_model', 'KalenderOrtModel');
|
||||
$this->ci->load->model('education/Lehreinheit_model', 'LehreinheitModel');
|
||||
$this->ci->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel');
|
||||
$this->ci->load->model('education/LehreinheitMitarbeiter_model', 'LehreinheitMitarbeiterModel');
|
||||
$this->_ci->load->model('ressource/Kalender_model', 'KalenderModel');
|
||||
$this->_ci->load->model('ressource/Kalender_Lehreinheit_model', 'KalenderLehreinheitModel');
|
||||
$this->_ci->load->model('ressource/Kalender_Ort_model', 'KalenderOrtModel');
|
||||
$this->_ci->load->model('education/Lehreinheit_model', 'LehreinheitModel');
|
||||
$this->_ci->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel');
|
||||
$this->_ci->load->model('education/LehreinheitMitarbeiter_model', 'LehreinheitMitarbeiterModel');
|
||||
$this->_ci->load->model('ressource/Ort_model', 'OrtModel');
|
||||
|
||||
}
|
||||
|
||||
public function getRoomData($ort_kurzbz, $start_date, $end_date)
|
||||
private function _getBasePlan($start_date, $end_date)
|
||||
{
|
||||
$data = $this->ci->KalenderModel->addJoin('lehre.tbl_kalender_ort', 'kalender_id');
|
||||
$data = $this->ci->KalenderModel->loadWhere(array(
|
||||
'von >=' => $start_date,
|
||||
'bis <= '=>$end_date,
|
||||
'ort_kurzbz'=>$ort_kurzbz
|
||||
));
|
||||
$end_date = date('Y-m-d', strtotime($end_date . ' +1 day'));
|
||||
|
||||
$stundenplan_data = array();
|
||||
if(isSuccess($data) && hasData($data))
|
||||
$this->_ci->KalenderModel->addSelect('tbl_kalender.kalender_id,
|
||||
tbl_kalender.status_kurzbz,
|
||||
tbl_kalender.von,
|
||||
tbl_kalender.bis,
|
||||
tbl_kalender_ort.ort_kurzbz,
|
||||
tbl_lehreinheit.lehreinheit_id,
|
||||
tbl_lehreinheit.lehrveranstaltung_id,
|
||||
tbl_lehreinheit.lehrfach_id,
|
||||
tbl_lehreinheit.lehrform_kurzbz,
|
||||
tbl_lehrveranstaltung.oe_kurzbz,
|
||||
lehrfach.kurzbz AS lehrfach_kurzbz,
|
||||
lehrfach.bezeichnung AS lehrfach_bezeichnung,
|
||||
lehrfach.farbe,
|
||||
tbl_lehreinheitmitarbeiter.mitarbeiter_uid,
|
||||
tbl_person.vorname,
|
||||
tbl_paddKalenderEventerson.nachname,
|
||||
tbl_mitarbeiter.kurzbz AS ma_kurzbz');
|
||||
$this->_ci->KalenderModel->addJoin('lehre.tbl_kalender_lehreinheit', 'tbl_kalender.kalender_id = tbl_kalender_lehreinheit.kalender_id');
|
||||
$this->_ci->KalenderModel->addJoin('lehre.tbl_lehreinheit', 'tbl_kalender_lehreinheit.lehreinheit_id = tbl_lehreinheit.lehreinheit_id');
|
||||
$this->_ci->KalenderModel->addJoin('lehre.tbl_lehrveranstaltung', 'tbl_lehreinheit.lehrveranstaltung_id = tbl_lehrveranstaltung.lehrveranstaltung_id');
|
||||
$this->_ci->KalenderModel->addJoin('lehre.tbl_lehrveranstaltung lehrfach', 'tbl_lehreinheit.lehrfach_id = lehrfach.lehrveranstaltung_id', 'LEFT');
|
||||
$this->_ci->KalenderModel->addJoin('lehre.tbl_kalender_ort', 'tbl_kalender.kalender_id = tbl_kalender_ort.kalender_id', 'LEFT');
|
||||
$this->_ci->KalenderModel->addJoin('lehre.tbl_lehreinheitmitarbeiter', 'tbl_lehreinheit.lehreinheit_id = tbl_lehreinheitmitarbeiter.lehreinheit_id', 'LEFT');
|
||||
$this->_ci->KalenderModel->addJoin('public.tbl_mitarbeiter', 'tbl_mitarbeiter.mitarbeiter_uid = tbl_lehreinheitmitarbeiter.mitarbeiter_uid', 'LEFT');
|
||||
$this->_ci->KalenderModel->addJoin('public.tbl_benutzer', 'tbl_mitarbeiter.mitarbeiter_uid = tbl_benutzer.uid', 'LEFT');
|
||||
$this->_ci->KalenderModel->addJoin('public.tbl_person', 'tbl_person.person_id = tbl_benutzer.person_id', 'LEFT');
|
||||
|
||||
$this->_ci->KalenderModel->db->where('tbl_kalender.von >=', $start_date);
|
||||
$this->_ci->KalenderModel->db->where('tbl_kalender.bis <', $end_date);
|
||||
}
|
||||
|
||||
private function _mapEvents($data)
|
||||
{
|
||||
$stundenplan_data = [];
|
||||
|
||||
if (!isSuccess($data) || !hasData($data))
|
||||
return $stundenplan_data;
|
||||
|
||||
$events = [];
|
||||
|
||||
foreach (getData($data) as $row)
|
||||
{
|
||||
$data = getData($data);
|
||||
foreach($data as $rowstpl)
|
||||
$id = $row->kalender_id;
|
||||
|
||||
if (!isset($events[$id]))
|
||||
{
|
||||
$obj = new stdClass();
|
||||
$obj->type='lehreinheit';
|
||||
$von = new DateTime($row->von);
|
||||
$bis = new DateTime($row->bis);
|
||||
|
||||
$von = new DateTime($rowstpl->von);
|
||||
$bis = new DateTime($rowstpl->bis);
|
||||
$events[$id] = (object) [
|
||||
'type' => 'lehreinheit',
|
||||
'beginn' => $von->format('H:i:s'),
|
||||
'ende' => $bis->format('H:i:s'),
|
||||
'datum' => $von->format('Y-m-d'),
|
||||
'isostart' => $von->format('c'),
|
||||
'isoend' => $bis->format('c'),
|
||||
'tooltip' => 'tip',
|
||||
'status_kurzbz' => $row->status_kurzbz,
|
||||
'ort_kurzbz' => isset($row->ort_kurzbz) ? $row->ort_kurzbz : '',
|
||||
'lehrform' => isset($row->lehrform_kurzbz) ? $row->lehrform_kurzbz : '',
|
||||
'lehrfach' => isset($row->lehrfach_kurzbz) ? $row->lehrfach_kurzbz : '',
|
||||
'lehrfach_bez' => isset($row->lehrfach_bezeichnung) ? $row->lehrfach_bezeichnung : '',
|
||||
'farbe' => isset($row->farbe) ? $row->farbe : '',
|
||||
'lehrveranstaltung_id' => $row->lehrveranstaltung_id,
|
||||
'organisationseinheit' => isset($row->oe_kurzbz) ? $row->oe_kurzbz : '',
|
||||
'kalender_id' => $id,
|
||||
'lehreinheit_id' => [],
|
||||
'lektor' => [],
|
||||
'gruppe' => [],
|
||||
'titel' => '',
|
||||
'topic' => (isset($row->lehrfach_kurzbz) ? $row->lehrfach_kurzbz : '').' '.(isset($row->lehrform_kurzbz) ? $row->lehrform_kurzbz : ''),
|
||||
];
|
||||
}
|
||||
|
||||
$obj->beginn = $von->format('H:i:s');
|
||||
$obj->ende = $bis->format('H:i:s');
|
||||
$obj->datum = $von->format('Y-m-d');
|
||||
$obj->topic = 'undefined';
|
||||
$obj->lektor = array();
|
||||
$obj->gruppe = array();
|
||||
$obj->isostart = $von->format('c');
|
||||
$obj->isoend = $bis->format('c');
|
||||
$obj->tooltip = 'tip';
|
||||
if ($row->lehreinheit_id && !in_array($row->lehreinheit_id, $events[$id]->lehreinheit_id))
|
||||
$events[$id]->lehreinheit_id[] = $row->lehreinheit_id;
|
||||
|
||||
$obj->lehreinheit_id = array();
|
||||
|
||||
$lehreinheiten = $this->ci->KalenderLehreinheitModel->loadWhere(array('kalender_id'=>$rowstpl->kalender_id));
|
||||
if(isSuccess($lehreinheiten) && hasData($lehreinheiten))
|
||||
if ($row->mitarbeiter_uid)
|
||||
{
|
||||
if (!in_array($row->mitarbeiter_uid, array_column($events[$id]->lektor, 'mitarbeiter_uid')))
|
||||
{
|
||||
$lehreinheiten = getData($lehreinheiten);
|
||||
foreach($lehreinheiten as $le)
|
||||
{
|
||||
$obj->lehreinheit_id[] = $le->lehreinheit_id;
|
||||
|
||||
$lehreinheitdata = $this->ci->LehreinheitModel->loadWhere(array('lehreinheit_id'=>$le->lehreinheit_id));
|
||||
|
||||
if(isSuccess($lehreinheitdata) && hasData($lehreinheitdata))
|
||||
{
|
||||
$ledata = getData($lehreinheitdata)[0];
|
||||
|
||||
|
||||
$lvid = $ledata->lehrveranstaltung_id;
|
||||
$lehrfach_id = $ledata->lehrfach_id;
|
||||
$obj->lehrform = $ledata->lehrform_kurzbz;
|
||||
|
||||
$lehreinheitmitarbeiterdata = $this->ci->LehreinheitMitarbeiterModel->loadWhere(array('lehreinheit_id'=>$le->lehreinheit_id));
|
||||
$lemitarbeiterdata = getData($lehreinheitmitarbeiterdata);
|
||||
|
||||
foreach($lemitarbeiterdata as $rowma)
|
||||
{
|
||||
$obj->lektor[] = array(
|
||||
"mitarbeiter_uid"=> $rowma->mitarbeiter_uid,
|
||||
"vorname"=>$rowma->mitarbeiter_uid,
|
||||
"nachname"=>$rowma->mitarbeiter_uid,
|
||||
"kurzbz"=>$rowma->mitarbeiter_uid
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
$events[$id]->lektor[] = [
|
||||
'mitarbeiter_uid' => $row->mitarbeiter_uid,
|
||||
'vorname' => $row->vorname,
|
||||
'nachname' => $row->nachname,
|
||||
'kurzbz' => $row->ma_kurzbz,
|
||||
];
|
||||
}
|
||||
|
||||
$lehrfachdata = $this->ci->LehrveranstaltungModel->loadWhere(array('lehrveranstaltung_id' => $lehrfach_id));
|
||||
$lfdata = getData($lehrfachdata)[0];
|
||||
|
||||
$lehrveranstaltungdata = $this->ci->LehrveranstaltungModel->loadWhere(array('lehrveranstaltung_id' => $lvid));
|
||||
$lvdata = getData($lehrveranstaltungdata)[0];
|
||||
|
||||
$obj->topic = $lfdata->kurzbz.' '.$obj->lehrform;
|
||||
|
||||
$orte = $this->ci->KalenderOrtModel->loadWhere(array('kalender_id'=>$rowstpl->kalender_id));
|
||||
$obj->ort_kurzbz = '';
|
||||
if(isSuccess($orte) && hasData($orte))
|
||||
{
|
||||
$ortedata = getdata($orte);
|
||||
foreach($ortedata as $ort);
|
||||
{
|
||||
$obj->ort_kurzbz .= $ort->ort_kurzbz;
|
||||
}
|
||||
}
|
||||
$obj->titel = '';
|
||||
$obj->lehrfach = $lfdata->kurzbz;
|
||||
$obj->lehrfach_bez = $lfdata->bezeichnung;
|
||||
$obj->organisationseinheit = $lvdata->oe_kurzbz;
|
||||
$obj->farbe = $lfdata->farbe;
|
||||
$obj->lehrveranstaltung_id = $lvid;
|
||||
$obj->kalender_id = $rowstpl->kalender_id;
|
||||
|
||||
$stundenplan_data[] = $obj;
|
||||
}
|
||||
}
|
||||
return $stundenplan_data;
|
||||
|
||||
return array_values($events);
|
||||
}
|
||||
public function getPlanByOrt($start_date, $end_date, $ort)
|
||||
{
|
||||
$this->_getBasePlan($start_date, $end_date);
|
||||
|
||||
$this->_ci->KalenderModel->db->where('tbl_kalender_ort.ort_kurzbz', $ort);
|
||||
$data = $this->_ci->KalenderModel->load();
|
||||
|
||||
return $this->_mapEvents($data);
|
||||
}
|
||||
|
||||
public function addKalenderEvent($user, $ort_kurzbz, $start_date, $end_date, $lehreinheit_id)
|
||||
public function getRaumvorschlagByLehreinheitID($start_date, $end_date, $lehreinheit_id)
|
||||
{
|
||||
$kalenderresult = $this->ci->KalenderModel->insert(array(
|
||||
'von' => $start_date,
|
||||
'bis' => $end_date,
|
||||
'typ' => 'lehreinheit',
|
||||
'status_kurzbz' => 'planning',
|
||||
'insertvon' => $user,
|
||||
'insertamum' => date('Y-m-d H:i:s')
|
||||
));
|
||||
$end_date = date('Y-m-d', strtotime($end_date . ' +1 day'));
|
||||
$lehreinheit = $this->_ci->LehreinheitModel->load(array('lehreinheit_id' => $lehreinheit_id));
|
||||
|
||||
if (isError($lehreinheit))
|
||||
return $lehreinheit;
|
||||
|
||||
if (!hasData($lehreinheit))
|
||||
return error("Not found");
|
||||
|
||||
$lehreinheit = getData($lehreinheit)[0];
|
||||
|
||||
$this->_ci->KalenderModel->addDistinct('ort_kurzbz');
|
||||
$this->_ci->KalenderModel->addSelect('ort_kurzbz');
|
||||
$this->_ci->KalenderModel->addJoin('lehre.tbl_kalender_ort', 'kalender_id');
|
||||
$this->_ci->KalenderModel->db->where('tbl_kalender.von >=', $start_date);
|
||||
$this->_ci->KalenderModel->db->where('tbl_kalender.bis <', $end_date);
|
||||
$belegte_raeume = $this->_ci->KalenderModel->load();
|
||||
|
||||
if (isError($belegte_raeume))
|
||||
return $belegte_raeume;
|
||||
|
||||
$belegte_orte = hasData($belegte_raeume) ? array_column(getData($belegte_raeume), 'ort_kurzbz') : [];
|
||||
|
||||
$vorschlaege = $this->_getFreieRaeume($lehreinheit->raumtyp, $belegte_orte);
|
||||
|
||||
if (isError($vorschlaege))
|
||||
return $vorschlaege;
|
||||
|
||||
$vorschlaege = hasData($vorschlaege) ? getData($vorschlaege) : [];
|
||||
|
||||
if (count($vorschlaege) < 5 && !empty($lehreinheit->raumtypalternativ))
|
||||
{
|
||||
$bereits_gefunden = array_merge($belegte_orte, array_column($vorschlaege, 'ort_kurzbz'));
|
||||
|
||||
$alternativ_raeume = $this->_getFreieRaeume($lehreinheit->raumtypalternativ, $bereits_gefunden);
|
||||
|
||||
if (!isError($alternativ_raeume) && hasData($alternativ_raeume))
|
||||
$vorschlaege = array_merge($vorschlaege, getData($alternativ_raeume));
|
||||
}
|
||||
|
||||
return success($vorschlaege);
|
||||
}
|
||||
|
||||
private function _getFreieRaeume($raumtyp, $belegte_orte)
|
||||
{
|
||||
$this->_ci->OrtModel->addSelect('ort_kurzbz');
|
||||
$this->_ci->OrtModel->addJoin('public.tbl_ortraumtyp', 'ort_kurzbz');
|
||||
$this->_ci->OrtModel->db->where('raumtyp_kurzbz', $raumtyp);
|
||||
$this->_ci->OrtModel->db->where('aktiv', true);
|
||||
$this->_ci->OrtModel->db->where("ort_kurzbz NOT LIKE '\_%'", null, false);
|
||||
|
||||
if (!empty($belegte_orte))
|
||||
$this->_ci->OrtModel->db->where_not_in('ort_kurzbz', $belegte_orte);
|
||||
$this->_ci->OrtModel->addOrder('hierarchie, ort_kurzbz');
|
||||
|
||||
return $this->_ci->OrtModel->load();
|
||||
}
|
||||
|
||||
public function getPlanNew($start_date, $end_date, $ort = null, $uids = null, $stg = null)
|
||||
{
|
||||
$this->_getBasePlan($start_date, $end_date);
|
||||
|
||||
if (!is_null($ort))
|
||||
$this->_ci->KalenderModel->db->where('tbl_kalender_ort.ort_kurzbz', $ort);
|
||||
|
||||
if (!is_null($uids))
|
||||
$this->_ci->KalenderModel->db->where_in('tbl_lehreinheitmitarbeiter.mitarbeiter_uid', $uids);
|
||||
|
||||
if (!is_null($stg))
|
||||
$this->_ci->KalenderModel->db->where('tbl_lehrveranstaltung.studiengang_kz', $stg);
|
||||
|
||||
$data = $this->_ci->KalenderModel->load();
|
||||
|
||||
return $this->_mapEvents($data);
|
||||
}
|
||||
public function getPlan($start_date, $end_date, $ort = null, $uids = null, $stg = null)
|
||||
{
|
||||
$end_date = date('Y-m-d', strtotime($end_date . ' +1 day'));
|
||||
|
||||
$this->_ci->KalenderModel->addSelect('tbl_kalender.kalender_id,
|
||||
tbl_kalender.status_kurzbz,
|
||||
tbl_kalender.von,
|
||||
tbl_kalender.bis,
|
||||
tbl_kalender_ort.ort_kurzbz,
|
||||
tbl_lehreinheit.lehreinheit_id,
|
||||
tbl_lehreinheit.lehrveranstaltung_id,
|
||||
tbl_lehreinheit.lehrfach_id,
|
||||
tbl_lehreinheit.lehrform_kurzbz,
|
||||
tbl_lehrveranstaltung.oe_kurzbz,
|
||||
lehrfach.kurzbz AS lehrfach_kurzbz,
|
||||
lehrfach.bezeichnung AS lehrfach_bezeichnung,
|
||||
lehrfach.farbe,
|
||||
tbl_lehreinheitmitarbeiter.mitarbeiter_uid,
|
||||
tbl_person.vorname,
|
||||
tbl_person.nachname,
|
||||
tbl_mitarbeiter.kurzbz AS ma_kurzbz');
|
||||
$this->_ci->KalenderModel->addJoin('lehre.tbl_kalender_lehreinheit', 'tbl_kalender.kalender_id = tbl_kalender_lehreinheit.kalender_id');
|
||||
$this->_ci->KalenderModel->addJoin('lehre.tbl_lehreinheit', 'tbl_kalender_lehreinheit.lehreinheit_id = tbl_lehreinheit.lehreinheit_id');
|
||||
$this->_ci->KalenderModel->addJoin('lehre.tbl_lehrveranstaltung', 'tbl_lehreinheit.lehrveranstaltung_id = tbl_lehrveranstaltung.lehrveranstaltung_id');
|
||||
$this->_ci->KalenderModel->addJoin('lehre.tbl_lehrveranstaltung lehrfach', 'tbl_lehreinheit.lehrfach_id = lehrfach.lehrveranstaltung_id', 'LEFT');
|
||||
$this->_ci->KalenderModel->addJoin('lehre.tbl_kalender_ort', 'tbl_kalender.kalender_id = tbl_kalender_ort.kalender_id', 'LEFT');
|
||||
$this->_ci->KalenderModel->addJoin('lehre.tbl_lehreinheitmitarbeiter', 'tbl_lehreinheit.lehreinheit_id = tbl_lehreinheitmitarbeiter.lehreinheit_id', 'LEFT');
|
||||
$this->_ci->KalenderModel->addJoin('public.tbl_mitarbeiter', 'tbl_mitarbeiter.mitarbeiter_uid = tbl_lehreinheitmitarbeiter.mitarbeiter_uid', 'LEFT');
|
||||
$this->_ci->KalenderModel->addJoin('public.tbl_benutzer', 'tbl_mitarbeiter.mitarbeiter_uid = tbl_benutzer.uid', 'LEFT');
|
||||
$this->_ci->KalenderModel->addJoin('public.tbl_person', 'tbl_person.person_id = tbl_benutzer.person_id', 'LEFT');
|
||||
|
||||
$this->_ci->KalenderModel->db->where('tbl_kalender.von >=', $start_date);
|
||||
$this->_ci->KalenderModel->db->where('tbl_kalender.bis <', $end_date);
|
||||
|
||||
if (!is_null($ort))
|
||||
$this->_ci->KalenderModel->db->where('tbl_kalender_ort.ort_kurzbz', $ort);
|
||||
|
||||
if (!is_null($uids))
|
||||
$this->_ci->KalenderModel->db->where_in('tbl_lehreinheitmitarbeiter.mitarbeiter_uid', $uids);
|
||||
|
||||
if (!is_null($stg))
|
||||
$this->_ci->KalenderModel->db->where('tbl_lehrveranstaltung.studiengang_kz', $stg);
|
||||
|
||||
$data = $this->_ci->KalenderModel->load();
|
||||
|
||||
$stundenplan_data = [];
|
||||
|
||||
if (!isSuccess($data) || !hasData($data))
|
||||
return $stundenplan_data;
|
||||
|
||||
$events = [];
|
||||
|
||||
foreach (getData($data) as $row)
|
||||
{
|
||||
$id = $row->kalender_id;
|
||||
|
||||
if (!isset($events[$id]))
|
||||
{
|
||||
$von = new DateTime($row->von);
|
||||
$bis = new DateTime($row->bis);
|
||||
|
||||
$events[$id] = (object) [
|
||||
'type' => 'lehreinheit',
|
||||
'beginn' => $von->format('H:i:s'),
|
||||
'ende' => $bis->format('H:i:s'),
|
||||
'datum' => $von->format('Y-m-d'),
|
||||
'isostart' => $von->format('c'),
|
||||
'isoend' => $bis->format('c'),
|
||||
'tooltip' => 'tip',
|
||||
'status_kurzbz' => $row->status_kurzbz,
|
||||
'ort_kurzbz' => isset($row->ort_kurzbz) ? $row->ort_kurzbz : '',
|
||||
'lehrform' => isset($row->lehrform_kurzbz) ? $row->lehrform_kurzbz : '',
|
||||
'lehrfach' => isset($row->lehrfach_kurzbz) ? $row->lehrfach_kurzbz : '',
|
||||
'lehrfach_bez' => isset($row->lehrfach_bezeichnung) ? $row->lehrfach_bezeichnung : '',
|
||||
'farbe' => isset($row->farbe) ? $row->farbe : '',
|
||||
'lehrveranstaltung_id' => $row->lehrveranstaltung_id,
|
||||
'organisationseinheit' => isset($row->oe_kurzbz) ? $row->oe_kurzbz : '',
|
||||
'kalender_id' => $id,
|
||||
'lehreinheit_id' => [],
|
||||
'lektor' => [],
|
||||
'gruppe' => [],
|
||||
'titel' => '',
|
||||
'topic' => (isset($row->lehrfach_kurzbz) ? $row->lehrfach_kurzbz : '') . ' ' . (isset($row->lehrform_kurzbz) ? $row->lehrform_kurzbz : ''),
|
||||
];
|
||||
}
|
||||
|
||||
if ($row->lehreinheit_id && !in_array($row->lehreinheit_id, $events[$id]->lehreinheit_id))
|
||||
$events[$id]->lehreinheit_id[] = $row->lehreinheit_id;
|
||||
|
||||
if ($row->mitarbeiter_uid)
|
||||
{
|
||||
if (!in_array($row->mitarbeiter_uid, array_column($events[$id]->lektor, 'mitarbeiter_uid')))
|
||||
{
|
||||
$events[$id]->lektor[] = [
|
||||
'mitarbeiter_uid' => $row->mitarbeiter_uid,
|
||||
'vorname' => $row->vorname,
|
||||
'nachname' => $row->nachname,
|
||||
'kurzbz' => $row->ma_kurzbz,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($events);
|
||||
}
|
||||
public function getZeitsperren($start_date, $end_date, $emp)
|
||||
{
|
||||
$db = new DB_Model();
|
||||
$qry = "
|
||||
SELECT
|
||||
tbl_zeitsperre.zeitsperre_id,
|
||||
tbl_zeitsperre.vondatum AS start,
|
||||
tbl_zeitsperre.bisdatum AS ende,
|
||||
tbl_vonstunde.beginn AS startstunde,
|
||||
tbl_bisstunde.ende AS bisstunde,
|
||||
tbl_erreichbarkeit.farbe AS erreichbarkeit_farbe,
|
||||
tbl_erreichbarkeit.beschreibung AS erreichbarkeit_beschreibung,
|
||||
tbl_zeitsperretyp.beschreibung as label
|
||||
FROM campus.tbl_zeitsperre
|
||||
JOIN campus.tbl_zeitsperretyp ON tbl_zeitsperre.zeitsperretyp_kurzbz = tbl_zeitsperretyp.zeitsperretyp_kurzbz
|
||||
LEFT JOIN campus.tbl_erreichbarkeit ON tbl_zeitsperre.erreichbarkeit_kurzbz = tbl_erreichbarkeit.erreichbarkeit_kurzbz
|
||||
LEFT JOIN lehre.tbl_stunde tbl_vonstunde ON tbl_zeitsperre.vonstunde = tbl_vonstunde.stunde
|
||||
LEFT JOIN lehre.tbl_stunde tbl_bisstunde ON tbl_zeitsperre.bisstunde = tbl_bisstunde.stunde
|
||||
WHERE tbl_zeitsperre.mitarbeiter_uid = ?
|
||||
AND tbl_zeitsperre.bisdatum >= ?
|
||||
AND tbl_zeitsperre.vondatum <= ?
|
||||
ORDER BY tbl_zeitsperre.vondatum;
|
||||
";
|
||||
|
||||
$result = $db->execReadOnlyQuery($qry, array($emp, $start_date, $end_date));
|
||||
|
||||
if (isError($result))
|
||||
return $result;
|
||||
|
||||
$zeitsperren_array = array();
|
||||
|
||||
if (hasData($result))
|
||||
{
|
||||
foreach (getData($result) as $zeitsperre)
|
||||
{
|
||||
$obj = new stdClass();
|
||||
$von = new DateTime($zeitsperre->start . ' '. $zeitsperre->startstunde);
|
||||
$bis = new DateTime($zeitsperre->ende . ' '. $zeitsperre->bisstunde);
|
||||
$obj->isostart = $von->format('c');
|
||||
$obj->isoend = $bis->format('c');
|
||||
$obj->label = $zeitsperre->label;
|
||||
$zeitsperren_array[] = $obj;
|
||||
}
|
||||
}
|
||||
return $zeitsperren_array;
|
||||
}
|
||||
public function getZeitwuensche($start_date, $end_date, $emp)
|
||||
{
|
||||
$db = new DB_Model();
|
||||
$qry = "
|
||||
WITH zeitwuensche AS (
|
||||
SELECT
|
||||
tbl_zeitwunsch.*,
|
||||
zw_gueltigkeit.von AS gueltig_von,
|
||||
zw_gueltigkeit.bis AS gueltig_bis
|
||||
FROM campus.tbl_zeitwunsch
|
||||
JOIN campus.tbl_zeitwunsch_gueltigkeit zw_gueltigkeit USING (zeitwunsch_gueltigkeit_id)
|
||||
WHERE tbl_zeitwunsch.mitarbeiter_uid = ?
|
||||
),
|
||||
tage AS (
|
||||
SELECT
|
||||
tage::date AS tag,
|
||||
EXTRACT(DOW FROM tage)::int AS wochentag
|
||||
FROM generate_series(?::date, ?::date, interval '1 day') AS tage
|
||||
)
|
||||
SELECT
|
||||
tage.tag,
|
||||
zeitwuensche.gewicht,
|
||||
tage.tag + s.beginn as start,
|
||||
tage.tag + s.ende as ende,
|
||||
mitarbeiter_uid as label
|
||||
FROM tage
|
||||
JOIN zeitwuensche ON tage.wochentag = zeitwuensche.tag
|
||||
AND tage.tag >= zeitwuensche.gueltig_von
|
||||
AND (zeitwuensche.gueltig_bis IS NULL OR tage.tag <= zeitwuensche.gueltig_bis)
|
||||
JOIN lehre.tbl_stunde s ON s.stunde = zeitwuensche.stunde
|
||||
ORDER BY tage.tag, start;";
|
||||
|
||||
$result = $db->execReadOnlyQuery($qry, array($emp, $start_date, $end_date));
|
||||
|
||||
if (isError($result))
|
||||
return $result;
|
||||
|
||||
$zeitwuensche_array = array();
|
||||
|
||||
if (hasData($result))
|
||||
{
|
||||
foreach (getData($result) as $zeitwuensch)
|
||||
{
|
||||
$obj = new stdClass();
|
||||
|
||||
$von = new DateTime($zeitwuensch->start);
|
||||
$bis = new DateTime($zeitwuensch->ende);
|
||||
|
||||
$obj->isostart = $von->format('c');
|
||||
$obj->isoend = $bis->format('c');
|
||||
$obj->gewicht = $zeitwuensch->gewicht;
|
||||
$obj->label = $zeitwuensch->label;
|
||||
$zeitwuensche_array[] = $obj;
|
||||
}
|
||||
}
|
||||
|
||||
return $zeitwuensche_array;
|
||||
|
||||
}
|
||||
public function addKalenderEvent($start_date, $end_date, $lehreinheit_id, $ort_kurzbz)
|
||||
{
|
||||
$kalenderresult = $this->_ci->KalenderModel->insert(
|
||||
array (
|
||||
'von' => $start_date,
|
||||
'bis' => $end_date,
|
||||
'typ' => 'lehreinheit',
|
||||
'status_kurzbz' => 'planning',
|
||||
'insertvon' => getAuthUID(),
|
||||
'insertamum' => date('Y-m-d H:i:s')
|
||||
)
|
||||
);
|
||||
|
||||
if(isSuccess($kalenderresult) && hasData($kalenderresult))
|
||||
{
|
||||
$kalender_id = getData($kalenderresult);
|
||||
|
||||
$kalenderlehreinheitresult = $this->_ci->KalenderLehreinheitModel->insert(
|
||||
array (
|
||||
'kalender_id' => $kalender_id,
|
||||
'lehreinheit_id' => $lehreinheit_id
|
||||
)
|
||||
);
|
||||
|
||||
$kalenderlehreinheitresult = $this->ci->KalenderLehreinheitModel->insert(array(
|
||||
'kalender_id' => $kalender_id,
|
||||
'lehreinheit_id' => $lehreinheit_id
|
||||
));
|
||||
|
||||
if(isSuccess($kalenderlehreinheitresult))
|
||||
if(isSuccess($kalenderlehreinheitresult) && !is_null($ort_kurzbz))
|
||||
{
|
||||
$kalenderOrtresult = $this->ci->KalenderOrtModel->insert(array(
|
||||
'kalender_id'=>$kalender_id,
|
||||
'ort_kurzbz'=>$ort_kurzbz
|
||||
));
|
||||
return $this->_addKalenderOrt($kalender_id, $ort_kurzbz);
|
||||
}
|
||||
|
||||
return $kalenderlehreinheitresult;
|
||||
}
|
||||
}
|
||||
|
||||
public function updateKalenderEvent($user, $kalender_id, $ort_kurzbz, $start_date, $end_date)
|
||||
private function _addKalenderOrt($kalender_id, $ort_kurzbz)
|
||||
{
|
||||
return $this->_ci->KalenderOrtModel->insert(
|
||||
array (
|
||||
'kalender_id'=>$kalender_id,
|
||||
'ort_kurzbz'=>$ort_kurzbz
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function updateOrt($kalender_id, $ort_kurzbz)
|
||||
{
|
||||
$exist = $this->_ci->KalenderOrtModel->load(array('kalender_id' => $kalender_id));
|
||||
|
||||
if (hasData($exist))
|
||||
{
|
||||
return $this->_ci->KalenderOrtModel->update(array('kalender_id' => $kalender_id),
|
||||
array (
|
||||
'ort_kurzbz' => $ort_kurzbz
|
||||
)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
return $this->_addKalenderOrt($kalender_id, $ort_kurzbz);
|
||||
}
|
||||
|
||||
}
|
||||
public function updateZeit($kalender_id, $start_date, $end_date)
|
||||
{
|
||||
/*TODO Checks:
|
||||
Von-Tag muss gleich dem Bis-Tag sein
|
||||
@@ -165,13 +486,15 @@ class KalenderLib
|
||||
History erstellen
|
||||
Sync Status setzen
|
||||
*/
|
||||
$this->ci->KalenderModel->update($kalender_id,
|
||||
array(
|
||||
'von'=>$start_date,
|
||||
'updateamum'=>date('Y-m-d H:i:s'),
|
||||
'updatevon' => $user
|
||||
$this->_ci->KalenderModel->update($kalender_id,
|
||||
array (
|
||||
'von' => $start_date,
|
||||
'bis' => $end_date,
|
||||
'updateamum'=> date('Y-m-d H:i:s'),
|
||||
'updatevon' => getAuthUID()
|
||||
)
|
||||
);
|
||||
|
||||
return success();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
class Kalenderstatus_model extends DB_Model
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->dbTable = 'lehre.tbl_kalender_status';
|
||||
$this->pk = 'status_kurzbz';
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,9 @@
|
||||
'bootstrap5' => true,
|
||||
'fontawesome6' => true,
|
||||
'vue3' => true,
|
||||
'vuedatepicker11' => true,
|
||||
'primevue3' => true,
|
||||
'tabulator5' => true,
|
||||
'vuedatepicker11' => true,
|
||||
'phrases' => array(
|
||||
'global',
|
||||
'ui',
|
||||
@@ -17,7 +17,9 @@
|
||||
'public/css/components/vue-datepicker.css',
|
||||
'public/css/components/primevue.css',
|
||||
'public/css/components/calendar.css',
|
||||
'public/css/Tempus.css'
|
||||
'public/css/Tempus.css',
|
||||
'public/css/Studentenverwaltung.css',
|
||||
'public/css/components/function.css'
|
||||
],
|
||||
'customJSs' => [
|
||||
#'vendor/npm-asset/primevue/tree/tree.min.js',
|
||||
@@ -37,6 +39,8 @@
|
||||
active-addons="<?= defined('ACTIVE_ADDONS') ? ACTIVE_ADDONS : ''; ?>"
|
||||
tempus-root="<?= site_url('Tempus'); ?>"
|
||||
cis-root="<?= CIS_ROOT; ?>"
|
||||
avatar-url="<?= site_url('Cis/Pub/bild/person/' . getAuthPersonId()); ?>"
|
||||
logout-url="<?= site_url('Cis/Auth/logout'); ?>"
|
||||
:permissions="<?= htmlspecialchars(json_encode($permissions)); ?>"
|
||||
:config="<?= htmlspecialchars(json_encode($variables)); ?>"
|
||||
>
|
||||
|
||||
+115
-35
@@ -77,48 +77,26 @@ body {
|
||||
color: var(--bs-success);
|
||||
}
|
||||
|
||||
#parkinglot {
|
||||
|
||||
|
||||
|
||||
#parkingslot {
|
||||
border: 1px dashed;
|
||||
width: 300px;
|
||||
height: 100px;
|
||||
max-height: 400px;
|
||||
color: #AAAAAA;
|
||||
text-align: center;
|
||||
font-size: large;
|
||||
margin-top: 20px;
|
||||
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
|
||||
#coursechooser {
|
||||
width: 300px;
|
||||
min-height: 100px;
|
||||
font-size: large;
|
||||
margin-top: 50px;
|
||||
border: 1px solid #ccc;
|
||||
background: #eee;
|
||||
text-align: left;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
#coursechooserheader {
|
||||
font-weight: bold;
|
||||
font-size: medium;
|
||||
}
|
||||
|
||||
#coursechooserfooter {
|
||||
font-size: small;
|
||||
color: #AAAAAA;
|
||||
}
|
||||
|
||||
.eckerl {
|
||||
width: 200px;
|
||||
margin: 10px 0;
|
||||
padding: 2px 4px;
|
||||
background: #00649c;
|
||||
color: #fff;
|
||||
font-size: .85em;
|
||||
cursor: pointer;
|
||||
border-radius: 2px;
|
||||
box-shadow: 3px 3px 3px #bbb;
|
||||
.parkingevent {
|
||||
border: 1px dashed !important;
|
||||
border-color: #AAAAAA;
|
||||
padding: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
margin-left: 0.5rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
:root{
|
||||
@@ -128,3 +106,105 @@ body {
|
||||
.eckerltest {
|
||||
box-shadow: 3px 3px 3px #ccc;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.fhc-resizable {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.fhc-resizable .fhc-resize-bar
|
||||
{
|
||||
position:absolute; left:6px; right:6px; height:10px;
|
||||
opacity:0; pointer-events:none; transition:opacity .12s ease;
|
||||
cursor: ns-resize; z-index: 20;
|
||||
}
|
||||
.fhc-resizable:hover .fhc-resize-bar{ opacity:1; pointer-events:auto; }
|
||||
|
||||
.fhc-resize-bar--top{ top:-4px; }
|
||||
.fhc-resize-bar--bottom{ bottom:-4px; }
|
||||
|
||||
.fhc-resize-bar::after{
|
||||
content:""; display:block; margin:3px auto 0;
|
||||
width:44px; height:4px; border-radius:999px;
|
||||
background: rgba(0,0,0,.35);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.verband-selection {
|
||||
border: 1px dashed #AAAAAA;
|
||||
margin-bottom: 0.5rem;
|
||||
max-height: 450px;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.lecture-selection {
|
||||
border: 1px dashed #AAAAAA;
|
||||
margin-bottom: 0.5rem;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.course-picker {
|
||||
border: 1px dashed #AAAAAA;
|
||||
height: 400px;
|
||||
max-height: 400px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.course-picker-row {
|
||||
padding: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
border: 1px solid var(--bs-border-color);
|
||||
background-color: var(--bs-tertiary-bg);
|
||||
}
|
||||
|
||||
.room-selection {
|
||||
border: 1px dashed #AAAAAA;
|
||||
margin-bottom: 0.5rem;
|
||||
padding: 0.5rem 0.0rem 0.5rem 0.5rem;
|
||||
}
|
||||
|
||||
.btn-link.text-danger {
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.btn-link.text-danger:hover {
|
||||
color: #dc3545;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.bg-lecturer-wish {
|
||||
opacity: .15;
|
||||
}
|
||||
.bg-lecturer-block {
|
||||
background: rgba(255, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
|
||||
.wish-w--2 {
|
||||
background-color: #FF2200;
|
||||
}
|
||||
.wish-w--1 {
|
||||
background-color: #FF9922;
|
||||
}
|
||||
.wish-w-1 {
|
||||
background-color: #CCFFCC;
|
||||
}
|
||||
.wish-w-2 {
|
||||
background-color: #48FA66;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
cursor: pointer;
|
||||
font-size: var(--fhc-calendar-fontsize-event, .875rem);
|
||||
}
|
||||
.event--parked {
|
||||
opacity: 0.45 !important;
|
||||
}
|
||||
|
||||
.fhc-calendar-mode-day .fhc-calendar-base-grid-line,
|
||||
.fhc-calendar-mode-week .fhc-calendar-base-grid-line {
|
||||
padding: 0 var(--fhc-calendar-gap-events, var(--fhc-calendar-gap, 1px));
|
||||
@@ -69,3 +73,36 @@
|
||||
.fhc-calendar-base .event > *:hover {
|
||||
filter: brightness(120%);
|
||||
}
|
||||
|
||||
.fhc-resize-bar {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
height: 15px;
|
||||
flex: 0 0 12px;
|
||||
cursor: ns-resize;
|
||||
position: relative;
|
||||
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.fhc-resize-bar--top {
|
||||
margin-bottom: -16px;
|
||||
}
|
||||
.fhc-resize-bar--bottom {
|
||||
margin-top: -16px;
|
||||
}
|
||||
|
||||
.fhc-calendar-base-grid-line-event:hover > .fhc-resize-bar {
|
||||
visibility: visible;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.fhc-calendar-base-grid-line-event.event:hover {
|
||||
z-index: 50;
|
||||
}
|
||||
.fhc-resize-bar {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
|
||||
export default {
|
||||
getCourses(searchfilter) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/tempus/getCourses',
|
||||
params: { searchfilter }
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -1,60 +0,0 @@
|
||||
|
||||
export default {
|
||||
getRoomplan(ort_kurzbz, start_date, end_date) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/Kalender/getRoomplan',
|
||||
params: { ort_kurzbz, start_date, end_date }
|
||||
};
|
||||
},
|
||||
getStundenplan(start_date, end_date) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/Kalender/getStundenplan',
|
||||
params: { start_date, end_date}
|
||||
};
|
||||
},
|
||||
getStunden() {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/Kalender/Stunden',
|
||||
params: {}
|
||||
};
|
||||
},
|
||||
getOrtReservierungen(ort_kurzbz, start_date, end_date) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: `/api/frontend/v1/Kalender/Reservierungen/${ort_kurzbz}`,
|
||||
params: { start_date, end_date}
|
||||
};
|
||||
},
|
||||
getStundenplanReservierungen(start_date, end_date) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/Kalender/Reservierungen',
|
||||
params: { start_date, end_date }
|
||||
};
|
||||
},
|
||||
getLehreinheitStudiensemester(lehreinheit_id) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: `/api/frontend/v1/Kalender/getLehreinheitStudiensemester/${lehreinheit_id}`,
|
||||
params: {}
|
||||
};
|
||||
},
|
||||
updateKalenderEvent(kalender_id, ort_kurzbz, start_date, end_date) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/Kalender/updateKalenderEvent',
|
||||
params: { kalender_id, ort_kurzbz, start_date, end_date}
|
||||
};
|
||||
},
|
||||
addKalenderEvent(lehreinheit_id, ort_kurzbz, start_date, end_date) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/Kalender/addKalenderEvent',
|
||||
params: { lehreinheit_id, ort_kurzbz, start_date, end_date}
|
||||
};
|
||||
},
|
||||
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (C) 2025 fhcomplete.org
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export default {
|
||||
get() {
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/tempus/config/get'
|
||||
};
|
||||
},
|
||||
getHeader() {
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/tempus/config/getHeader'
|
||||
};
|
||||
},
|
||||
set(params) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/tempus/config/set',
|
||||
params
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
export default {
|
||||
search(query) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/tempus/coursepicker/search',
|
||||
params: { query }
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
|
||||
export default {
|
||||
getPlan(filter, start_date, end_date)
|
||||
{
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/tempus/Kalender/getPlan',
|
||||
params: { ...filter, start_date, end_date }
|
||||
};
|
||||
},
|
||||
getLektorZeitsperren(emp, start_date, end_date) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/tempus/Kalender/getZeitsperren',
|
||||
params: { emp, start_date, end_date }
|
||||
};
|
||||
},
|
||||
getLektorZeitwuensche(emp, start_date, end_date) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/tempus/Kalender/getZeitwuensche',
|
||||
params: { emp, start_date, end_date }
|
||||
};
|
||||
},
|
||||
getStunden() {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/tempus/Kalender/getStunden',
|
||||
};
|
||||
},
|
||||
updateKalenderEvent(kalender_id, updatedInfos) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/tempus/Kalender/updateKalenderEvent',
|
||||
params: { kalender_id, updatedInfos}
|
||||
};
|
||||
},
|
||||
addKalenderEvent(lehreinheit_id, ort_kurzbz, start_date, end_date) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/tempus/Kalender/addKalenderEvent',
|
||||
params: { lehreinheit_id, ort_kurzbz, start_date, end_date}
|
||||
};
|
||||
},
|
||||
getRaumvorschlag(start_date, end_date, lehreinheit_id) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/tempus/Kalender/getRaumvorschlag',
|
||||
params: { start_date, end_date, lehreinheit_id}
|
||||
};
|
||||
},
|
||||
|
||||
};
|
||||
@@ -16,7 +16,6 @@
|
||||
*/
|
||||
|
||||
import FhcTempus from "../components/Tempus/Tempus.js";
|
||||
import fhcapifactory from "./api/fhcapifactory.js";
|
||||
|
||||
import Phrasen from "../plugins/Phrasen.js";
|
||||
|
||||
|
||||
@@ -1,23 +1,31 @@
|
||||
import BaseDraganddrop from './Base/DragAndDrop.js';
|
||||
import BaseHeader from './Base/Header.js';
|
||||
import BaseSlider from './Base/Slider.js';
|
||||
import BsModal from '../Bootstrap/Modal.js';
|
||||
|
||||
import CalClick from '../../directives/Calendar/Click.js';
|
||||
import DragClick from '../../directives/dragClick.js';
|
||||
import Draggable from '../../directives/draggable.js';
|
||||
import Drop from '../../directives/drop.js';
|
||||
|
||||
export default {
|
||||
name: "CalendarBase",
|
||||
components: {
|
||||
BaseDraganddrop,
|
||||
BaseHeader,
|
||||
BaseSlider,
|
||||
BsModal
|
||||
},
|
||||
directives: {
|
||||
CalClick
|
||||
CalClick,
|
||||
DragClick,
|
||||
Draggable,
|
||||
Drop
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
events: Vue.computed(() => this.convertedEvents),
|
||||
backgrounds: Vue.computed(() => this.convertedBackgrounds),
|
||||
dropAllowed: Vue.computed(() => !!this.onDrop),
|
||||
onDrop: Vue.computed(() => this.onDrop || null),
|
||||
locale: Vue.computed(() => this.locale),
|
||||
timezone: Vue.computed(() => this.timezone),
|
||||
timeGrid: Vue.computed(() => this.timeGrid),
|
||||
@@ -43,6 +51,17 @@ export default {
|
||||
|
||||
return () => true;
|
||||
}),
|
||||
resizableEvents: Vue.computed(() => {
|
||||
if (!this.resizableEvents)
|
||||
return () => false;
|
||||
|
||||
if (Array.isArray(this.resizableEvents))
|
||||
return event => this.resizableEvents.includes(event.type);
|
||||
if (this.resizableEvents instanceof Function)
|
||||
return this.resizableEvents;
|
||||
|
||||
return () => true;
|
||||
}),
|
||||
hasDragoverFunc: Vue.computed(() => this.onDragover),
|
||||
mode: Vue.computed(() => this.mode)
|
||||
};
|
||||
@@ -93,9 +112,14 @@ export default {
|
||||
type: Boolean,
|
||||
default: undefined
|
||||
},
|
||||
btnTableList: {
|
||||
type: Boolean,
|
||||
default: undefined
|
||||
},
|
||||
timeGrid: Array,
|
||||
draggableEvents: [Boolean, Array, Function],
|
||||
dropableEvents: [Boolean, Array, Function],
|
||||
resizableEvents: [Boolean, Array, Function],
|
||||
onDragover: Function,
|
||||
onDrop: Function
|
||||
},
|
||||
@@ -240,9 +264,7 @@ export default {
|
||||
break;
|
||||
}
|
||||
},
|
||||
onDropItem(evt, start, end) {
|
||||
this.$emit('drop', evt, start, end);
|
||||
},
|
||||
|
||||
showEventModal(eventObj) {
|
||||
this.modalEvent = eventObj;
|
||||
this.$refs.modal.show();
|
||||
@@ -263,11 +285,7 @@ export default {
|
||||
},
|
||||
template: /* html */`
|
||||
<div class="fhc-calendar-base h-100">
|
||||
<base-draganddrop
|
||||
class="card h-100"
|
||||
:events="convertedEvents"
|
||||
:backgrounds="convertedBackgrounds"
|
||||
@drop="onDropItem"
|
||||
<div class="card h-100"
|
||||
v-cal-click:container
|
||||
@cal-click-default.capture="handleClickDefaults"
|
||||
>
|
||||
@@ -282,6 +300,7 @@ export default {
|
||||
:btn-week="!!modes['week'] && (btnWeek || (showBtns && btnWeek !== false))"
|
||||
:btn-month="!!modes['month'] && (btnMonth || (showBtns && btnMonth !== false))"
|
||||
:btn-list="!!modes['list'] && (btnList || (showBtns && btnList !== false))"
|
||||
:btn-table-list="!!modes['tableList'] && (btnTableList || (showBtns && btnTableList !== false))"
|
||||
:mode-options="modeOptions ? modeOptions[cMode] : undefined"
|
||||
>
|
||||
<slot name="actions" />
|
||||
@@ -293,11 +312,12 @@ export default {
|
||||
@update:range="$emit('update:range', $event)"
|
||||
@request-modal-open="showEventModal"
|
||||
@request-modal-close="hideEventModal"
|
||||
@drop="$emit('drop', $event)"
|
||||
v-bind="modeOptions ? modeOptions[cMode] : null || {}"
|
||||
>
|
||||
<template v-slot="slot"><slot v-bind="slot" /></template>
|
||||
</component>
|
||||
</base-draganddrop>
|
||||
</div>
|
||||
<bs-modal ref="modal" dialog-class="modal-lg" body-class="" @hidden-bs-modal="onModalHidden">
|
||||
<template #title>
|
||||
<slot v-if="modalEvent" v-bind="{mode: 'eventheader', event: modalEvent.event}" />
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
import DragAndDrop from '../../../helpers/DragAndDrop.js';
|
||||
|
||||
import CalDnd from '../../../directives/Calendar/DragAndDrop.js';
|
||||
|
||||
/**
|
||||
* TODO(chris): this needs serious rework!
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: "CalendarDragAndDrop",
|
||||
directives: {
|
||||
CalDnd
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
events: Vue.computed(() => this.correctedEvents),
|
||||
backgrounds: Vue.computed(() => this.backgrounds),
|
||||
dropAllowed: Vue.computed(() => this.dragging && this.dropAllowed)
|
||||
};
|
||||
},
|
||||
inject: {
|
||||
mode: "mode",
|
||||
dropableEvents: "dropableEvents"
|
||||
},
|
||||
props: {
|
||||
events: Array,
|
||||
backgrounds: Array
|
||||
},
|
||||
emits: [
|
||||
"drop"
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
dragging: false,
|
||||
allowed: false,
|
||||
draggedInternalEvent: null,
|
||||
draggedExternalEvent: null,
|
||||
targetTimestamp: 0,
|
||||
targetGridEnds: null,
|
||||
dropAllowed: false,
|
||||
|
||||
shadowPreview: false // TODO(chris): IMPLEMENT! (use background instead of event as preview)
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
correctedEvents() {
|
||||
if (this.dragging) {
|
||||
if (this.draggedInternalEvent) {
|
||||
const index = this.events.findIndex(e => e.id == this.draggedInternalEvent.id);
|
||||
if (this.previewEvent && !this.shadowPreview)
|
||||
return this.events.toSpliced(index, 1, this.previewEvent);
|
||||
else
|
||||
return this.events.toSpliced(index, 1);
|
||||
}
|
||||
if (this.previewEvent && !this.shadowPreview)
|
||||
return [...this.events, this.previewEvent];
|
||||
}
|
||||
|
||||
return this.events;
|
||||
},
|
||||
correctedBackgrounds() {
|
||||
if (this.dragging) {
|
||||
if (this.shadowPreview) {
|
||||
// TODO(chris): how to get the length
|
||||
return [...this.backgrounds, {
|
||||
start: new Date(this.targetTimestamp),
|
||||
class: 'shadow-preview'
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
return this.backgrounds;
|
||||
},
|
||||
previewEvent() {
|
||||
if (!this.dragging || !this.dropAllowed)
|
||||
return null;
|
||||
if (!this.targetTimestamp)
|
||||
return null;
|
||||
|
||||
const event = this.draggedInternalEvent || this.draggedExternalEvent;
|
||||
|
||||
if (!event)
|
||||
return null;
|
||||
|
||||
// TODO(chris): calculate length correctly from orig
|
||||
let length = event.end - event.start;
|
||||
if (this.targetGridEnds)
|
||||
length = this.targetGridEnds.find(end => end >= this.targetTimestamp + length) - this.targetTimestamp;
|
||||
|
||||
return {
|
||||
orig: event.orig,
|
||||
start: this.targetTimestamp,
|
||||
end: this.targetTimestamp + length
|
||||
};
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onDragstart(evt) {
|
||||
const data = DragAndDrop.convertToTransferData(evt.detail.item.orig);
|
||||
if (DragAndDrop.isValidDragObject(data)) {
|
||||
DragAndDrop.setTransferData(evt.detail.originalEvent, data);
|
||||
this.draggedInternalEvent = evt.detail.item;
|
||||
}
|
||||
},
|
||||
onDragend() {
|
||||
this.draggedInternalEvent = null;
|
||||
this.dragging = false;
|
||||
},
|
||||
onDragenter(evt) {
|
||||
this.dragging = true;
|
||||
|
||||
if (!this.draggedInternalEvent) {
|
||||
const event = DragAndDrop.getValidTransferData(evt.detail.originalEvent);
|
||||
if (event) {
|
||||
this.draggedExternalEvent = {
|
||||
id: event.id,
|
||||
type: event.type,
|
||||
start: event.isostart
|
||||
? luxon.DateTime.fromISO(event.isostart).setZone(this.timezone)
|
||||
: luxon.DateTime.local().setZone(this.timezone),
|
||||
end: event.isoend
|
||||
? luxon.DateTime.fromISO(event.isoend).setZone(this.timezone)
|
||||
: luxon.DateTime.local().setZone(this.timezone),
|
||||
orig: event
|
||||
};
|
||||
} else {
|
||||
this.draggedExternalEvent = null;
|
||||
}
|
||||
this.dropAllowed = this.dropableEvents(event, this.mode);
|
||||
} else {
|
||||
this.dropAllowed = this.dropableEvents(this.draggedInternalEvent, this.mode);
|
||||
}
|
||||
},
|
||||
onDragleave() {
|
||||
this.dragging = false;
|
||||
},
|
||||
onDragchange(evt) {
|
||||
this.targetTimestamp = evt.detail.timestamp;
|
||||
|
||||
this.targetGridEnds = evt.detail.ends || null;
|
||||
},
|
||||
onDrop(evt) {
|
||||
if (!this.dragging || !this.dropAllowed)
|
||||
return;
|
||||
|
||||
this.$emit('drop', evt, this.previewEvent.start, this.previewEvent.end);
|
||||
this.dropAllowed = false;
|
||||
this.dragging = false;
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div
|
||||
class="fhc-calendar-base-draganddrop"
|
||||
@calendar-dragstart="onDragstart"
|
||||
@calendar-dragend="onDragend"
|
||||
v-cal-dnd:dropcage
|
||||
@calendar-dragenter="onDragenter"
|
||||
@calendar-dragleave="onDragleave"
|
||||
@calendar-dragchange="onDragchange"
|
||||
@drop="onDrop"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import GridLine from './Grid/Line.js';
|
||||
import GridLineEvent from './Grid/Line/Event.js';
|
||||
|
||||
import CalDnd from '../../../directives/Calendar/DragAndDrop.js';
|
||||
import drop from '../../../directives/drop.js';
|
||||
import { useResizeHandler } from '../../../helpers/Tempus/ResizeHandler.js';
|
||||
|
||||
export default {
|
||||
name: "CalendarGrid",
|
||||
@@ -10,12 +11,17 @@ export default {
|
||||
GridLineEvent
|
||||
},
|
||||
directives: {
|
||||
CalDnd
|
||||
drop
|
||||
},
|
||||
inject: {
|
||||
originalEvents: "events",
|
||||
originalBackgrounds: "backgrounds",
|
||||
dropAllowed: "dropAllowed"
|
||||
dropAllowed: "dropAllowed",
|
||||
onDrop: "onDrop",
|
||||
timeGrid: {
|
||||
from: "timeGrid",
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
@@ -57,10 +63,10 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dragging: false,
|
||||
resizeObserver: null,
|
||||
mutationObserver: null,
|
||||
userScroll: true
|
||||
userScroll: true,
|
||||
isDragging: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -262,7 +268,9 @@ export default {
|
||||
mouseFrac = mouse / this.$refs.body.offsetHeight;
|
||||
}
|
||||
|
||||
return dayTimestamp + this.start + Math.floor((this.end - this.start) * mouseFrac);
|
||||
let rawTimestamp = dayTimestamp + this.start + Math.floor((this.end - this.start) * mouseFrac);
|
||||
let fiveMinutes = 5 * 60 * 1000;
|
||||
return Math.round(rawTimestamp / fiveMinutes) * fiveMinutes;
|
||||
},
|
||||
|
||||
/* SCROLLING */
|
||||
@@ -308,7 +316,157 @@ export default {
|
||||
} else {
|
||||
this.$refs.scroller.scrollTo(0, 0);
|
||||
}
|
||||
}
|
||||
},
|
||||
calculateNettoDuration(start, end) {
|
||||
const startDay = start.startOf('day');
|
||||
const blocks = this.axisPartsWithBreaks.filter(p => p.index !== undefined);
|
||||
|
||||
let nettoDuration = luxon.Duration.fromMillis(0);
|
||||
|
||||
for (const block of blocks)
|
||||
{
|
||||
const blockStart = startDay.plus(block.start);
|
||||
const blockEnd = startDay.plus(block.end);
|
||||
|
||||
const overlapStart = blockStart > start ? blockStart : start;
|
||||
const overlapEnd = blockEnd < end ? blockEnd : end;
|
||||
|
||||
if (overlapStart < overlapEnd) {
|
||||
nettoDuration = nettoDuration.plus(overlapEnd.diff(overlapStart));
|
||||
}
|
||||
}
|
||||
|
||||
return nettoDuration;
|
||||
},
|
||||
|
||||
calculateDropEnd(dropStart, durationMs) {
|
||||
const duration = luxon.Duration.fromMillis(durationMs);
|
||||
const blocks = this.axisPartsWithBreaks.filter(p => p.index !== undefined);
|
||||
let accumulated = luxon.Duration.fromMillis(0);
|
||||
|
||||
for (const block of blocks)
|
||||
{
|
||||
const blockStart = dropStart.startOf('day').plus(block.start);
|
||||
const blockEnd = dropStart.startOf('day').plus(block.end);
|
||||
|
||||
if (blockEnd <= dropStart) continue;
|
||||
|
||||
const relevantStart = blockStart > dropStart ? blockStart : dropStart;
|
||||
const relevantDuration = blockEnd.diff(relevantStart);
|
||||
|
||||
accumulated = accumulated.plus(relevantDuration);
|
||||
|
||||
if (accumulated >= duration)
|
||||
{
|
||||
const overflow = accumulated.minus(duration);
|
||||
return blockEnd.minus(overflow);
|
||||
}
|
||||
}
|
||||
|
||||
const lastBlock = blocks[blocks.length - 1];
|
||||
return dropStart.startOf('day').plus(lastBlock.end);
|
||||
},
|
||||
|
||||
onDropSnap(evt, items, date, part) {
|
||||
let obj = items?.[0];
|
||||
if (!obj?.orig) return;
|
||||
|
||||
const dayStr = evt?.currentTarget?.dataset?.day;
|
||||
const dropDay = dayStr ? luxon.DateTime.fromISO(dayStr) : date;
|
||||
|
||||
const dropStart = dropDay.plus(part.start || part);
|
||||
|
||||
let nettoDuration = this._getNettoDurationForDrop(obj);
|
||||
let dropEnd = this.calculateDropEnd(dropStart, nettoDuration);
|
||||
|
||||
this.onDrop?.({
|
||||
item: [obj],
|
||||
start: dropStart.toISO(),
|
||||
end: dropEnd.toISO()
|
||||
});
|
||||
},
|
||||
|
||||
_getNettoDurationForDrop(obj) {
|
||||
|
||||
if (!obj?.orig)
|
||||
return luxon.Duration.fromObject({ minutes: 45 });
|
||||
|
||||
if (obj.orig.isostart && obj.orig.isoend)
|
||||
{
|
||||
let s = luxon.DateTime.fromISO(obj.orig.isostart);
|
||||
let e = luxon.DateTime.fromISO(obj.orig.isoend);
|
||||
if (s.isValid && e.isValid)
|
||||
return this.calculateNettoDuration(s, e);
|
||||
}
|
||||
|
||||
if (obj.type === 'lehreinheit')
|
||||
{
|
||||
let blockung = Number(obj.orig.blockung ?? 1);
|
||||
if (!Number.isFinite(blockung) || blockung <= 0) blockung = 1;
|
||||
|
||||
let blocks = this.axisPartsWithBreaks.filter(p => p.index !== undefined);
|
||||
let firstBlock = blocks[0];
|
||||
|
||||
let bStart = luxon.Duration.fromISO(firstBlock.start);
|
||||
let bEnd = luxon.Duration.fromISO(firstBlock.end);
|
||||
|
||||
let blockMinutes = bEnd.minus(bStart).as('minutes');
|
||||
if (!Number.isFinite(blockMinutes) || blockMinutes <= 0) blockMinutes = 45;
|
||||
|
||||
return luxon.Duration.fromObject({ minutes: blockung * blockMinutes });
|
||||
}
|
||||
|
||||
return luxon.Duration.fromObject({ minutes: 45 });
|
||||
},
|
||||
onDropFree(evt, items, date)
|
||||
{
|
||||
let obj = items?.[0];
|
||||
if (!obj?.orig)
|
||||
return;
|
||||
|
||||
const timestamp = this.getTimestampFromMouse(evt, date);
|
||||
const dropStart = luxon.DateTime.fromMillis(timestamp);
|
||||
|
||||
let nettoDuration = this._getNettoDurationForDrop(obj);
|
||||
let dropEnd = this.calculateDropEnd(dropStart, nettoDuration);
|
||||
|
||||
|
||||
this.onDrop?.({
|
||||
item: items,
|
||||
start: dropStart.toISO(),
|
||||
end: dropEnd.toISO()
|
||||
});
|
||||
},
|
||||
handleResizeStart({ edge, evt, el, event })
|
||||
{
|
||||
const gridEl = this.$refs.body;
|
||||
|
||||
if (!gridEl)
|
||||
return;
|
||||
|
||||
this.resizeHandler.startResize(edge, evt, {
|
||||
el,
|
||||
gridEl,
|
||||
event,
|
||||
timeGrid: this.timeGrid,
|
||||
onEnd: ({ event, newStart, newEnd }) => {
|
||||
const orig = event?.orig;
|
||||
if (!orig || !newStart || !newEnd)
|
||||
return;
|
||||
|
||||
this.onDrop?.({
|
||||
item: [{ type: 'kalender', id: orig.kalender_id, orig }],
|
||||
start: newStart,
|
||||
end: newEnd
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
setup()
|
||||
{
|
||||
const resizeHandler = useResizeHandler();
|
||||
return { resizeHandler };
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.disableAutoScroll();
|
||||
@@ -382,11 +540,11 @@ export default {
|
||||
ref="body"
|
||||
class="grid-body"
|
||||
style="display:grid;grid-template-rows:subgrid;grid-template-columns:subgrid"
|
||||
@dragenter="isDragging = true"
|
||||
@dragleave.self="isDragging = false"
|
||||
@dragend="isDragging = false"
|
||||
@drop="isDragging = false"
|
||||
:style="'grid-' + axisCol + ':2/-1;grid-' + axisRow + ':1/-1'"
|
||||
v-cal-dnd:dropcage
|
||||
@calendar-dragenter="dragging = true"
|
||||
@calendar-dragleave="dragging = false"
|
||||
@dragover="dropAllowed ? $event.preventDefault() : null"
|
||||
>
|
||||
<template
|
||||
v-for="(date, index) in axisMain"
|
||||
@@ -401,9 +559,11 @@ export default {
|
||||
>
|
||||
<slot name="part-body" v-bind="{ index, part }" />
|
||||
<div
|
||||
v-if="snapToGrid && dragging"
|
||||
style="position:absolute;inset:0;z-index:1"
|
||||
v-cal-dnd:dropzone.once="{date: date.plus(part.start || part), ends: ends.slice(ends.findIndex(end => end > date))}"
|
||||
v-if="snapToGrid"
|
||||
style="position:absolute;inset:0"
|
||||
:style="{ zIndex: isDragging ? 10 : 1 }"
|
||||
:data-day="date.toFormat('yyyy-MM-dd')"
|
||||
v-drop:move.lehreinheit-collection.kalender-collection="(evt, item) => onDropSnap(evt, item, date, part)"
|
||||
></div>
|
||||
</div>
|
||||
<grid-line
|
||||
@@ -413,6 +573,7 @@ export default {
|
||||
:events="eventsNormal[index]"
|
||||
:backgrounds="backgrounds[index]"
|
||||
style="position:relative"
|
||||
@resize-start="handleResizeStart"
|
||||
:style="'grid-' + axisRow + ':1/-1;grid-' + axisCol + ':' + (1+index)"
|
||||
>
|
||||
<template #event="slot">
|
||||
@@ -420,9 +581,10 @@ export default {
|
||||
</template>
|
||||
<template #dropzone>
|
||||
<div
|
||||
v-if="!snapToGrid && dragging"
|
||||
style="position:absolute;inset:0;z-index:1"
|
||||
v-cal-dnd:dropzone="evt => getTimestampFromMouse(evt, date)"
|
||||
v-if="!snapToGrid"
|
||||
style="position:absolute;inset:0"
|
||||
:style="{ zIndex: isDragging ? 10 : 1 }"
|
||||
v-drop:move.lehreinheit-collection.kalender-collection="(evt, item) => onDropFree(evt, item, date)"
|
||||
></div>
|
||||
</template>
|
||||
</grid-line>
|
||||
@@ -432,4 +594,4 @@ export default {
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
@@ -74,6 +74,7 @@ export default {
|
||||
:key="i"
|
||||
:style="'grid-' + axisRow + ': ' + event.rows.join('/')"
|
||||
:event="event"
|
||||
@resize-start="$emit('resize-start', $event)"
|
||||
>
|
||||
<template v-slot="slot">
|
||||
<slot name="event" v-bind="slot" />
|
||||
|
||||
@@ -1,15 +1,35 @@
|
||||
import CalDnd from '../../../../../directives/Calendar/DragAndDrop.js';
|
||||
import draggable from '../../../../../directives/draggable.js';
|
||||
import CalClick from '../../../../../directives/Calendar/Click.js';
|
||||
|
||||
export default {
|
||||
name: "GridLineEvent",
|
||||
directives: {
|
||||
CalDnd,
|
||||
draggable,
|
||||
CalClick
|
||||
},
|
||||
emits: [
|
||||
'resize-start'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
contextMenu: {
|
||||
show: false,
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
};
|
||||
},
|
||||
inject: {
|
||||
draggableEvents: "draggableEvents",
|
||||
mode: "mode"
|
||||
resizableEvents: {
|
||||
from: "resizableEvents",
|
||||
default: () => () => false
|
||||
},
|
||||
mode: "mode",
|
||||
contextMenuActions: {
|
||||
from: "contextMenuActions",
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
props: {
|
||||
event: {
|
||||
@@ -27,6 +47,9 @@ export default {
|
||||
draggable() {
|
||||
return !this.isHeaderOrFooter && this.draggableEvents(this.event.orig, this.mode);
|
||||
},
|
||||
resizable() {
|
||||
return !this.isHeaderOrFooter && this.resizableEvents(this.event.orig, this.mode);
|
||||
},
|
||||
classes() {
|
||||
const classes = [];
|
||||
if (this.isHeaderOrFooter) {
|
||||
@@ -37,21 +60,93 @@ export default {
|
||||
if (this.event.endsHere)
|
||||
classes.push('event-end');
|
||||
}
|
||||
return classes
|
||||
return classes;
|
||||
},
|
||||
dragKalenderCollection() {
|
||||
const orig = this.event.orig;
|
||||
return {
|
||||
type: 'kalender',
|
||||
id: orig?.kalender_id ?? null,
|
||||
orig,
|
||||
};
|
||||
},
|
||||
activeContextActions() {
|
||||
if (this.isHeaderOrFooter) return [];
|
||||
const type = this.event.orig?.type ?? 'lehreinheit';
|
||||
return this.contextMenuActions[type] ?? this.contextMenuActions['default'] ?? [];
|
||||
}
|
||||
},
|
||||
template: /* html */`
|
||||
methods: {
|
||||
onResizeStart(edge, evt) {
|
||||
this.$emit('resize-start', {
|
||||
edge,
|
||||
evt,
|
||||
el: this.$refs.eventEl,
|
||||
event: this.event
|
||||
});
|
||||
},
|
||||
onRightClick(evt) {
|
||||
|
||||
this.contextMenu.show = true;
|
||||
this.contextMenu.x = evt.clientX;
|
||||
this.contextMenu.y = evt.clientY;
|
||||
},
|
||||
onContextAction(action) {
|
||||
this.contextMenu.show = false;
|
||||
action(this.event.orig);
|
||||
},
|
||||
closeContextMenu() {
|
||||
this.contextMenu.show = false;
|
||||
}
|
||||
},
|
||||
template:`
|
||||
<div
|
||||
class="fhc-calendar-base-grid-line-event event"
|
||||
:class="classes"
|
||||
style="z-index: 1"
|
||||
style="z-index: 2"
|
||||
:draggable="draggable"
|
||||
v-cal-dnd:draggable="event"
|
||||
ref="eventEl"
|
||||
v-draggable:move.noimage="draggable ? dragKalenderCollection : {}"
|
||||
v-cal-click:event="isHeaderOrFooter ? event : event.orig"
|
||||
@contextmenu.prevent="onRightClick"
|
||||
>
|
||||
<div
|
||||
v-if="resizable"
|
||||
class="fhc-resize-bar fhc-resize-bar--top"
|
||||
@pointerdown.prevent.stop="onResizeStart('start', $event)"
|
||||
@click.stop
|
||||
/>
|
||||
<slot :event="isHeaderOrFooter ? event : event.orig">
|
||||
{{ event.orig }}
|
||||
</slot>
|
||||
<div
|
||||
v-if="resizable"
|
||||
class="fhc-resize-bar fhc-resize-bar--bottom"
|
||||
@pointerdown.prevent.stop="onResizeStart('end', $event)"
|
||||
@click.stop
|
||||
/>
|
||||
|
||||
|
||||
<teleport to="body">
|
||||
<div
|
||||
v-if="contextMenu.show"
|
||||
style="position:fixed; inset:0; z-index:9998"
|
||||
@click="closeContextMenu"
|
||||
@contextmenu.prevent="closeContextMenu"
|
||||
/>
|
||||
<ul
|
||||
v-if="contextMenu.show"
|
||||
class="dropdown-menu show"
|
||||
:style="{ position: 'fixed', top: contextMenu.y + 'px', left: contextMenu.x + 'px', zIndex: 9999 }"
|
||||
>
|
||||
<li v-for="action in activeContextActions" :key="action.label">
|
||||
<button class="dropdown-item" type="button" @click.stop="onContextAction(action.action)">
|
||||
<i v-if="action.icon" :class="action.icon + ' me-2'"></i>
|
||||
{{ action.label }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</teleport>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import draggable from '../../../../../directives/draggable.js';
|
||||
|
||||
export default {
|
||||
name: 'EventCard',
|
||||
directives: {
|
||||
draggable,
|
||||
},
|
||||
props: {
|
||||
event: { type: Object, required: true },
|
||||
parked: Boolean
|
||||
},
|
||||
computed: {
|
||||
dragKalenderCollection() {
|
||||
return this.event
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<div
|
||||
class="fhc-calendar-base-grid-line-event event"
|
||||
v-draggable:move.noimage="dragKalenderCollection"
|
||||
style="border:1px"
|
||||
>
|
||||
<div class="title">
|
||||
{{ event.orig.topic || event.orig.titel || event.orig.lehrfach }}
|
||||
</div>
|
||||
<div>
|
||||
{{ event.orig.datum }} {{ event.orig.beginn }}–{{ event.orig.ende }}
|
||||
<span v-if="event.ort_kurzbz">· {{ event.orig.ort_kurzbz }}</span>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
};
|
||||
@@ -24,7 +24,8 @@ export default {
|
||||
btnMonth: Boolean,
|
||||
btnWeek: Boolean,
|
||||
btnDay: Boolean,
|
||||
btnList: Boolean
|
||||
btnList: Boolean,
|
||||
btnTableList: Boolean
|
||||
},
|
||||
emits: [
|
||||
"next",
|
||||
@@ -89,6 +90,16 @@ export default {
|
||||
>
|
||||
<i class="fa fa-table-list"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-if="btnTableList"
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
:class="{active: mode === 'tableList'}"
|
||||
@click="clickMode($event, 'tableList')"
|
||||
>
|
||||
<i class="fa fa-table-list"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -39,6 +39,7 @@ export default {
|
||||
case "list":
|
||||
return [this.convertedDate.startOf('day').ts, this.convertedDate.startOf('day').plus({ days: this.listLength }).ts - 1];
|
||||
case "week":
|
||||
case "tableList":
|
||||
return [this.convertedDate.startOf('week', { useLocaleWeeks: true }).ts, this.convertedDate.endOf('week', { useLocaleWeeks: true }).ts];
|
||||
case "day":
|
||||
return this.convertedDate;
|
||||
@@ -51,6 +52,7 @@ export default {
|
||||
case "month":
|
||||
return this.date.toLocaleString({ month: 'long', year: 'numeric' });
|
||||
case "week":
|
||||
case "tableList":
|
||||
var year = this.date.localWeekYear;
|
||||
var week = this.date.toFormat('nn');
|
||||
return this.$p.t('calendar/year_kw', { year, week });
|
||||
@@ -76,6 +78,7 @@ export default {
|
||||
break;
|
||||
case "list":
|
||||
case "week":
|
||||
case "tableList":
|
||||
date = luxon.DateTime.fromJSDate(value[0]).setZone(this.timezone, { keepLocalTime: true }).setLocale(this.locale);
|
||||
break;
|
||||
case "day":
|
||||
@@ -96,7 +99,7 @@ export default {
|
||||
@update:model-value="update"
|
||||
:format="() => title"
|
||||
:month-picker="mode == 'month'"
|
||||
:week-picker="mode == 'week'"
|
||||
:week-picker="mode == 'week' || mode == 'tableList'"
|
||||
:range="mode == 'list' ? { autoRange: listLength - 1 } : false"
|
||||
:text-input="mode == 'day'"
|
||||
:week-start="weekStart"
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import LabelDay from '../../Base/Label/Day.js';
|
||||
import LabelDow from '../../Base/Label/Dow.js';
|
||||
|
||||
import CalDnd from '../../../../directives/Calendar/DragAndDrop.js';
|
||||
import draggable from '../../../../directives/draggable.js';
|
||||
|
||||
import CalClick from '../../../../directives/Calendar/Click.js';
|
||||
|
||||
// TODO(chris): drag and drop
|
||||
@@ -13,7 +14,7 @@ export default {
|
||||
LabelDow
|
||||
},
|
||||
directives: {
|
||||
CalDnd,
|
||||
draggable,
|
||||
CalClick
|
||||
},
|
||||
inject: {
|
||||
@@ -86,7 +87,7 @@ export default {
|
||||
v-else
|
||||
class="event"
|
||||
:draggable="draggable(event)"
|
||||
v-cal-dnd:draggable="event"
|
||||
v-draggable="event.orig"
|
||||
v-cal-click:event="event.orig"
|
||||
>
|
||||
<slot :event="event.orig" mode="list" />
|
||||
|
||||
@@ -36,7 +36,9 @@ export default {
|
||||
}
|
||||
}
|
||||
return events;
|
||||
})
|
||||
}),
|
||||
draggableEvents: () => false,
|
||||
resizableEvents: () => false
|
||||
};
|
||||
},
|
||||
inject: {
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
import BaseSlider from '../Base/Slider.js';
|
||||
import TableView from './Table/View.js';
|
||||
|
||||
export default {
|
||||
name: "ModeTable",
|
||||
components: {
|
||||
BaseSlider,
|
||||
TableView
|
||||
},
|
||||
props: {
|
||||
currentDate: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
"update:currentDate",
|
||||
"update:range",
|
||||
"click",
|
||||
"requestModalOpen"
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
focusDate: this.currentDate,
|
||||
rangeOffset: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
range() {
|
||||
let first = this.focusDate.startOf('week', { useLocaleWeeks: true });
|
||||
let last = this.focusDate.endOf('week', { useLocaleWeeks: true });
|
||||
|
||||
if (this.rangeOffset != 0) {
|
||||
if (this.rangeOffset < 0) {
|
||||
first = first.plus({ weeks: this.rangeOffset });
|
||||
} else {
|
||||
last = last.plus({ weeks: this.rangeOffset });
|
||||
}
|
||||
}
|
||||
|
||||
return luxon.Interval.fromDateTimes(first, last);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
currentDate() {
|
||||
if (this.currentDate.locale != this.focusDate.locale) {
|
||||
this.focusDate = this.currentDate;
|
||||
this.$emit('update:range', this.range);
|
||||
} else {
|
||||
this.rangeOffset = this.currentDate.startOf('week', { useLocaleWeeks: true }).diff(this.focusDate.startOf('week', { useLocaleWeeks: true }), 'weeks').weeks;
|
||||
if (this.rangeOffset) {
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.slider.slidePages(this.rangeOffset).then(this.updatePage);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
prevPage() {
|
||||
this.rangeOffset = this.$refs.slider.target - 1;
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.slider.prevPage().then(this.updatePage);
|
||||
},
|
||||
nextPage() {
|
||||
this.rangeOffset = this.$refs.slider.target + 1;
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.slider.nextPage().then(this.updatePage);
|
||||
},
|
||||
updatePage(weeks) {
|
||||
const newFocusDate = this.focusDate.plus({ weeks });
|
||||
this.focusDate = newFocusDate;
|
||||
this.rangeOffset = 0;
|
||||
this.$emit('update:currentDate', this.focusDate);
|
||||
this.$emit('update:range', this.range);
|
||||
},
|
||||
viewAttrs(weeks) {
|
||||
const day = this.focusDate.plus({ weeks });
|
||||
return { ...this.$attrs, day };
|
||||
},
|
||||
handleClickDefaults(evt) {
|
||||
|
||||
switch (evt.detail.source) {
|
||||
case 'day':
|
||||
// default: Set current-date
|
||||
this.$emit('update:currentDate', evt.detail.value);
|
||||
break;
|
||||
case 'event':
|
||||
// default: Request Modal
|
||||
this.$emit('requestModalOpen', { event: evt.detail.value });
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$emit('update:range', this.range);
|
||||
},
|
||||
template: `
|
||||
<div
|
||||
class="fhc-calendar-mode-week flex-grow-1 position-relative"
|
||||
@cal-click-default.capture="handleClickDefaults"
|
||||
>
|
||||
<base-slider ref="slider" v-slot="slot">
|
||||
<table-view ref="view" v-bind="viewAttrs(slot.offset)">
|
||||
<template v-slot="slot"><slot v-bind="slot" mode="week" /></template>
|
||||
</table-view>
|
||||
</base-slider>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import {CoreFilterCmpt} from "../../../../components/filter/Filter.js";
|
||||
import BsModal from '../../../Bootstrap/Modal.js';
|
||||
import FormInput from "../../../Form/Input.js";
|
||||
import ApiDetails from "../../../../api/lehrveranstaltung/details.js";
|
||||
|
||||
|
||||
export default {
|
||||
name: "TableView",
|
||||
inject: {
|
||||
events: "events",
|
||||
timezone: "timezone"
|
||||
},
|
||||
components: {
|
||||
CoreFilterCmpt,
|
||||
BsModal,
|
||||
FormInput
|
||||
},
|
||||
props: {
|
||||
day: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data()
|
||||
{
|
||||
return {
|
||||
raumtyp_array: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
start() {
|
||||
return this.day.startOf('week', { useLocaleWeeks: true });
|
||||
},
|
||||
preparedEvents() {
|
||||
const end = this.start.plus({ days: 7 });
|
||||
return this.events
|
||||
.filter(e => e.start < end && e.end > this.start)
|
||||
.sort((a, b) => a.start.ts - b.start.ts)
|
||||
.map(event => ({
|
||||
...event.orig,
|
||||
row_index: event.id,
|
||||
}));
|
||||
},
|
||||
tabulatorOptions() {
|
||||
return {
|
||||
index: "row_index",
|
||||
layout: 'fitDataStretch',
|
||||
placeholder: "Keine Daten verfügbar",
|
||||
persistenceID: "2026_03_09_table_view_v1",
|
||||
data: this.preparedEvents,
|
||||
columns: [
|
||||
{
|
||||
formatter: 'rowSelection',
|
||||
titleFormatter: 'rowSelection',
|
||||
titleFormatterParams: {
|
||||
rowRange: "active"
|
||||
},
|
||||
headerSort: false,
|
||||
width: 40
|
||||
},
|
||||
{title: 'Datum', field: 'datum', headerFilter: "input", formatter: (cell) => {
|
||||
let val = cell.getValue();
|
||||
if (!val)
|
||||
return ' ';
|
||||
return luxon.DateTime.fromISO(val).toFormat('dd.MM.yyyy')
|
||||
}
|
||||
},
|
||||
{title: 'Von', field: 'beginn', headerFilter: "input"},
|
||||
{title: 'Bis', field: 'ende', headerFilter: "input"},
|
||||
{title: 'Lehrfach', field: 'lehrfach', headerFilter: "input"},
|
||||
{title: 'Bezeichnung', field: 'lehrfach_bez', headerFilter: "input"},
|
||||
{title: 'Lehrform', field: 'lehrform', headerFilter: "input"},
|
||||
{title: 'Raum', field: 'ort_kurzbz', headerFilter: "input"},
|
||||
{
|
||||
title: 'Lektor',
|
||||
field: 'lektor',
|
||||
headerFilter: "input",
|
||||
mutator: (value) => {
|
||||
if (!value)
|
||||
return '';
|
||||
return value.map(l => l.kurzbz).join(', ') ?? '–'
|
||||
}
|
||||
},
|
||||
{title: 'OE', field: 'organisationseinheit', headerFilter: "input"},
|
||||
{title: 'Status', field: 'status_kurzbz', headerFilter: "input"},
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods:
|
||||
{
|
||||
openModal() {
|
||||
this.$refs.raumModal.show();
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
preparedEvents(newData) {
|
||||
this.$refs.tableViewTable?.tabulator?.setData(newData);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
this.$api.call(ApiDetails.getRaumtyp())
|
||||
.then(result => {
|
||||
this.raumtyp_array = result.data;
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
|
||||
},
|
||||
template: /* html */`
|
||||
<div class="fhc-calendar-mode-table-view h-100 overflow-auto">
|
||||
<core-filter-cmpt
|
||||
ref="tableViewTable"
|
||||
:tabulator-options="tabulatorOptions"
|
||||
:table-only="true"
|
||||
:side-menu="false"
|
||||
:download="true"
|
||||
>
|
||||
<template #actions>
|
||||
<button class="btn btn-outline-secondary btn-sm">Verschieben</button>
|
||||
<button @click="openModal" class="btn btn-outline-secondary btn-sm">Raum wechsel</button>
|
||||
</template>
|
||||
</core-filter-cmpt>
|
||||
|
||||
<bs-modal ref="raumModal" class="bootstrap-prompt" dialogClass="modal-lg">
|
||||
<template #title>Raum verschiebung</template>
|
||||
<form-input
|
||||
:label="$p.t('lehre', 'raumtyp')"
|
||||
type="select"
|
||||
container-class="col-3"
|
||||
name="raumtyp"
|
||||
>
|
||||
<option
|
||||
v-for="raumtyp in raumtyp_array"
|
||||
:value="raumtyp.raumtyp_kurzbz"
|
||||
:key="raumtyp.raumtyp_kurzbz"
|
||||
>
|
||||
{{ raumtyp.raumtyp_kurzbz }} {{ raumtyp.beschreibung }}
|
||||
</option>
|
||||
</form-input>
|
||||
<template #footer>
|
||||
<button type="button" class="btn btn-primary">{{ $p.t('ui', 'speichern') }}</button>
|
||||
</template>
|
||||
</bs-modal>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -7,15 +7,25 @@ import { useEventLoader } from '../../composables/EventLoader.js';
|
||||
import ModeDay from './Mode/Day.js';
|
||||
import ModeWeek from './Mode/Week.js';
|
||||
import ModeMonth from './Mode/Month.js';
|
||||
import ModeTable from './Mode/Table.js';
|
||||
import ApiTempusConfig from '../../api/factory/tempus/config.js';
|
||||
import ApiKalender from '../../api/factory/tempus/kalender.js';
|
||||
|
||||
|
||||
export default {
|
||||
name: "CalendarTempus",
|
||||
components: {
|
||||
FhcCalendar
|
||||
},
|
||||
inject: [
|
||||
"renderers"
|
||||
],
|
||||
inject: {
|
||||
renderers: {from: 'renderers'},
|
||||
appConfig: {
|
||||
from: 'appConfig',
|
||||
default: {
|
||||
visible_status: 'all'
|
||||
}
|
||||
}
|
||||
},
|
||||
props: {
|
||||
timezone: {
|
||||
type: String,
|
||||
@@ -32,6 +42,18 @@ export default {
|
||||
getPromiseFunc: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
parkedEvents: {
|
||||
type: Object,
|
||||
default: () => new Set()
|
||||
},
|
||||
visibleLecturers: {
|
||||
type: Array,
|
||||
default: null
|
||||
},
|
||||
extraBackgrounds: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
@@ -40,11 +62,13 @@ export default {
|
||||
"update:range",
|
||||
"drop"
|
||||
],
|
||||
|
||||
data() {
|
||||
return {
|
||||
modes: {
|
||||
week: Vue.markRaw(ModeWeek),
|
||||
month: Vue.markRaw(ModeMonth)
|
||||
month: Vue.markRaw(ModeMonth),
|
||||
tableList: Vue.markRaw(ModeTable),
|
||||
},
|
||||
modeOptions: {
|
||||
day: {
|
||||
@@ -55,31 +79,78 @@ export default {
|
||||
collapseEmptyDays: false
|
||||
}
|
||||
},
|
||||
teachingunits: null
|
||||
teachingunits: null,
|
||||
visibleStatusArray: [],
|
||||
visibleStatus: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
backgrounds() {
|
||||
let now = luxon.DateTime.now().setZone(this.timezone);
|
||||
|
||||
let past = [];
|
||||
if (this.mode == 'Month')
|
||||
return [
|
||||
{
|
||||
class: 'background-past',
|
||||
end: now.startOf('day')
|
||||
}
|
||||
];
|
||||
|
||||
return [
|
||||
{
|
||||
{
|
||||
past = [{
|
||||
class: 'background-past',
|
||||
end: now.startOf('day')
|
||||
}];
|
||||
}
|
||||
else
|
||||
{
|
||||
past = [{
|
||||
class: 'background-past',
|
||||
end: now,
|
||||
label: now.startOf('minute').toISOTime({ suppressSeconds: true, includeOffset: false })
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
return [
|
||||
...past,
|
||||
...(this.extraBackgrounds || [])
|
||||
];
|
||||
}
|
||||
},
|
||||
visibleEvents()
|
||||
{
|
||||
let list = this.events;
|
||||
|
||||
if (Array.isArray(this.visibleLecturers))
|
||||
{
|
||||
const visibleLectures = new Set(this.visibleLecturers);
|
||||
|
||||
list = list.filter(event => {
|
||||
if (!event.lektor?.length)
|
||||
return true;
|
||||
return event.lektor.some(lektor => visibleLectures.has(lektor.mitarbeiter_uid));
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.visibleStatus.length || this.visibleStatus.includes('all'))
|
||||
return list;
|
||||
|
||||
return list.filter(event => this.visibleStatus.includes(event.status_kurzbz));
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleStatus(status) {
|
||||
if (status === 'all')
|
||||
{
|
||||
this.visibleStatus = ['all'];
|
||||
return;
|
||||
}
|
||||
|
||||
this.visibleStatus = this.visibleStatus.filter(visibleStatus => visibleStatus !== 'all');
|
||||
|
||||
let found = this.visibleStatus.indexOf(status);
|
||||
|
||||
if (found === -1)
|
||||
this.visibleStatus.push(status);
|
||||
else
|
||||
this.visibleStatus.splice(found, 1);
|
||||
|
||||
if (this.visibleStatus.length < 1)
|
||||
this.visibleStatus.push('all');
|
||||
},
|
||||
eventStyle(event) {
|
||||
if (!event.farbe)
|
||||
return undefined;
|
||||
@@ -89,14 +160,18 @@ export default {
|
||||
this.rangeInterval = rangeInterval;
|
||||
this.$emit('update:range', rangeInterval);
|
||||
},
|
||||
ondrop(e, start, end){
|
||||
this.$emit('drop', e, start, end);
|
||||
}
|
||||
ondrop(payload){
|
||||
this.$emit('drop', payload);
|
||||
},
|
||||
resetEventLoader() {
|
||||
this.reset();
|
||||
},
|
||||
|
||||
},
|
||||
setup(props, context) {
|
||||
const rangeInterval = Vue.ref(null);
|
||||
|
||||
const { events, lv } = useEventLoader(rangeInterval, props.getPromiseFunc);
|
||||
const { events, lv, reset } = useEventLoader(rangeInterval, props.getPromiseFunc);
|
||||
|
||||
Vue.watch(lv, newValue => {
|
||||
context.emit('update:lv', newValue);
|
||||
@@ -105,12 +180,14 @@ export default {
|
||||
return {
|
||||
rangeInterval,
|
||||
events,
|
||||
lv
|
||||
lv,
|
||||
reset
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$api
|
||||
.call(ApiLvPlan.getStunden())
|
||||
.call(ApiKalender.getStunden())
|
||||
.then(res => {
|
||||
return this.teachingunits = res.data.map(el => ({
|
||||
id: el.stunde,
|
||||
@@ -118,6 +195,11 @@ export default {
|
||||
end: el.ende
|
||||
}));
|
||||
});
|
||||
this.$api.call(ApiTempusConfig.getHeader())
|
||||
.then(res => {
|
||||
this.visibleStatusArray = res.data.visible_status
|
||||
this.visibleStatus = ['all']
|
||||
});
|
||||
},
|
||||
template: /* html */`
|
||||
<fhc-calendar
|
||||
@@ -129,18 +211,20 @@ export default {
|
||||
:mode="mode"
|
||||
:timezone="timezone"
|
||||
:locale="$p.user_locale.value"
|
||||
:events="events || []"
|
||||
:events="visibleEvents || []"
|
||||
:backgrounds="backgrounds"
|
||||
:time-grid="teachingunits"
|
||||
show-btns
|
||||
@drop="ondrop"
|
||||
:draggable-events="true"
|
||||
:resizable-events="true"
|
||||
:on-drop="ondrop"
|
||||
@update:date="(newDate, newMode) => $emit('update:date', newDate, newMode)"
|
||||
@update:mode="(newMode, newDate) => $emit('update:mode', newMode, newDate)"
|
||||
@update:range="updateRange"
|
||||
>
|
||||
<template v-slot="{ event, mode }">
|
||||
<div
|
||||
:class="'event-type-' + event.type + ' ' + mode + 'PageContainer'"
|
||||
:class="['event-type-' + event.type + ' ' + mode + 'PageContainer', { 'event--parked': parkedEvents.has(String(event.kalender_id)) }]"
|
||||
:type="mode == 'day' ? 'button' : undefined"
|
||||
:style="eventStyle(event)"
|
||||
>
|
||||
@@ -162,7 +246,17 @@ export default {
|
||||
</div>
|
||||
</template>
|
||||
<template #actions>
|
||||
<slot />
|
||||
<slot>
|
||||
<button
|
||||
v-for="(status, key) in visibleStatusArray"
|
||||
:key="key"
|
||||
class="btn btn-sm me-1"
|
||||
:class="visibleStatus.includes(key) ? 'btn-secondary' : 'btn-outline-secondary'"
|
||||
@click="toggleStatus(key)"
|
||||
>
|
||||
{{ status }}
|
||||
</button>
|
||||
</slot>
|
||||
</template>
|
||||
</fhc-calendar>`
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export default {
|
||||
this.event.lektor.slice(0, 3).map(lektor => lektor.kurzbz).join("\n")
|
||||
+ "\n" + this.$p.t('lehre/weitereLektoren', [this.event.lektor.length - 3])
|
||||
].join(": "));
|
||||
} else {console.log(this.event.lektor);
|
||||
} else {
|
||||
tooltipArray.push([
|
||||
this.$p.t('lehre/lektor'),
|
||||
this.event.lektor.map(lektor => lektor.kurzbz).join("\n")
|
||||
@@ -64,7 +64,7 @@ export default {
|
||||
<span>{{ start }}</span>
|
||||
<span>{{ end }}</span>
|
||||
</div>
|
||||
<div class="event-text" v-tooltip="tooltipString">
|
||||
<div class="event-text">
|
||||
<span class="event-topic">{{ event.topic }}</span>
|
||||
<span class="event-place">{{ event.ort_kurzbz }}</span>
|
||||
<span
|
||||
|
||||
@@ -131,7 +131,7 @@ export default {
|
||||
},
|
||||
onSelectTreeNode(node) {
|
||||
if (node.data.link)
|
||||
this.$emit('selectVerband', {link: node.data.link, studiengang_kz: node.data.stg_kz, semester: node.data.semester, orgform_kurzbz: node.data.orgform_kurzbz});
|
||||
this.$emit('selectVerband', {link: node.data.link, studiengang_kz: node.data.stg_kz, semester: node.data.semester, orgform_kurzbz: node.data.orgform_kurzbz, name: node.data.name});
|
||||
},
|
||||
mapNodesToNoSemReloadNodes(result, node) {
|
||||
if (node.data.no_sem_reload)
|
||||
|
||||
@@ -1,55 +1,71 @@
|
||||
import ApiCoursePicker from '../../api/factory/coursepicker.js';
|
||||
import FormInput from '../Form/Input.js';
|
||||
import draggable from "../../directives/draggable.js";
|
||||
import ApiCoursePicker from '../../api/factory/tempus/coursepicker.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FormInput
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
};
|
||||
directives: {
|
||||
draggable
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
courses: null
|
||||
searchparam: '',
|
||||
courses: null,
|
||||
abortController: null,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
loadCourses: function(){
|
||||
async loadCourses() {
|
||||
|
||||
Promise.allSettled([
|
||||
this.$api.call(ApiCoursePicker.getCourses("test")),
|
||||
]).then((result) => {
|
||||
let promise_events = [];
|
||||
result.forEach((promise_result) => {
|
||||
if (promise_result.status === 'fulfilled' && promise_result.value.meta.status === "success") {
|
||||
let data = promise_result.value.data;
|
||||
if (data && data.forEach) {
|
||||
const query = (this.searchparam ?? '').trim();
|
||||
|
||||
data.forEach((entry, i) => {
|
||||
entry.showname = entry.studiengang_kurzbz+entry.semester+' - ' + entry.kurzbz + ' ' + entry.lektoren.toString();
|
||||
entry.tag = '';
|
||||
entry.mode = 'single';
|
||||
});
|
||||
if (!query)
|
||||
{
|
||||
this.courses = [];
|
||||
return;
|
||||
}
|
||||
|
||||
if (query.length < 3)
|
||||
return;
|
||||
|
||||
if (this.abortController)
|
||||
{
|
||||
this.abortController.abort();
|
||||
}
|
||||
|
||||
this.abortController = new AbortController();
|
||||
const signal = this.abortController.signal;
|
||||
|
||||
this.$api.call(ApiCoursePicker.search(query), { signal })
|
||||
.then(result => {
|
||||
this.courses = result.data.map(entry => ({
|
||||
lehreinheit_id: entry.lehreinheit_id,
|
||||
lektoren: entry.lektor,
|
||||
studiengang: entry.studiengang,
|
||||
semester: entry.semester,
|
||||
stundenblockung: entry.stundenblockung,
|
||||
wochenrythmus: entry.wochenrythmus,
|
||||
showname: `${entry.lehrfach} ${entry.lehrform}`,
|
||||
orig: {
|
||||
type: 'lehreinheit',
|
||||
lehreinheit_id: entry.lehreinheit_id,
|
||||
blockung: entry.stundenblockung,
|
||||
entry,
|
||||
}
|
||||
promise_events = promise_events.concat(data);
|
||||
}
|
||||
}));
|
||||
})
|
||||
this.courses = promise_events;
|
||||
|
||||
});
|
||||
.catch(this.$fhcAlert.handleSystemError)
|
||||
},
|
||||
dragstart: function(evt, course) {
|
||||
const transferdata = {
|
||||
dragLehreinheitCollection(course) {
|
||||
const orig = course.orig;
|
||||
return {
|
||||
type: 'lehreinheit',
|
||||
id: course.lehreinheit_id,
|
||||
mode: course.mode
|
||||
id: orig.lehreinheit_id,
|
||||
orig: orig,
|
||||
stundenblockung: course.stundenblockung,
|
||||
};
|
||||
|
||||
event.dataTransfer.setData('text', JSON.stringify(transferdata));
|
||||
},
|
||||
keydown: function(evt, course) {
|
||||
|
||||
@@ -64,26 +80,43 @@ export default {
|
||||
course.mode = 'multi';
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
created() {
|
||||
this.loadCourses();
|
||||
//document.addEventListener('keydown', function () { console.log("a"); });
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
template: /*html*/`
|
||||
<div ref="container" class="fhc-coursechooser">
|
||||
<div id="coursechooser">
|
||||
<span id="coursechooserheader">Course</span>
|
||||
<input type="text" placeholder="Search"/>
|
||||
<div v-for="course in courses" class="eckerl" draggable="true" @dragstart="dragstart(event, course)" tabindex="0" @keyup="keydown($event, course)">
|
||||
{{course.showname}}
|
||||
{{course.tag}}
|
||||
</div>
|
||||
<span id="coursechooserfooter">Drag & Drop on Calender</span>
|
||||
template: `
|
||||
<div class="course-picker">
|
||||
<div class="p-2">
|
||||
<form-input
|
||||
:label="$p.t('ui', 'suche')"
|
||||
type="text"
|
||||
v-model="searchparam"
|
||||
@input="loadCourses"
|
||||
/>
|
||||
</div>
|
||||
</div>`
|
||||
<div class="overflow-auto px-2 pb-2 flex-grow-1">
|
||||
<div
|
||||
v-for="course in courses"
|
||||
:key="course.lehreinheit_id"
|
||||
class="course-picker-row"
|
||||
v-draggable:move.noimage="dragLehreinheitCollection(course)"
|
||||
tabindex="0"
|
||||
>
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<span class="fw-semibold small">{{ course.showname }}</span>
|
||||
</div>
|
||||
<div class="text-muted">
|
||||
<span>{{ course.studiengang }} - {{ course.semester }}</span>
|
||||
</div>
|
||||
<div class="text-muted">
|
||||
<span>{{ course.lektoren }}</span>
|
||||
</div>
|
||||
<div class="text-muted">
|
||||
<span>WR: {{ course.wochenrythmus }} Bl: {{ course.stundenblockung }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-auto px-2 py-2 small text-muted border-top">
|
||||
Drag & Drop on Calendar
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import FormInput from '../Form/Input.js';
|
||||
|
||||
export default {
|
||||
name: "LectureSelection",
|
||||
props: {
|
||||
lecturers: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: ['remove'],
|
||||
template: `
|
||||
<div class="lecture-selection">
|
||||
<div v-for="l in lecturers" :key="l.uid">
|
||||
<div class="fw-semibold px-2 pt-2 d-flex align-items-center justify-content-between">
|
||||
{{ l.label }}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-link text-danger p-0"
|
||||
@click="$emit('remove', l.uid)"
|
||||
title="Lektor entfernen"
|
||||
>
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="overflow-auto flex-grow-1 px-2 pb-2">
|
||||
<div class="d-flex align-items-center gap-2" @click="l.showEvents = !l.showEvents" style="cursor:pointer">
|
||||
<i :class="l.showEvents ? 'fa-solid fa-toggle-on text-primary' : 'fa-solid fa-toggle-off text-muted'"></i>
|
||||
<span class="form-check-label">Plan</span>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2" @click="l.overlays.blocks = !l.overlays.blocks" style="cursor:pointer">
|
||||
<i :class="l.overlays.blocks ? 'fa-solid fa-toggle-on text-primary' : 'fa-solid fa-toggle-off text-muted'"></i>
|
||||
<span class="form-check-label">Zeitsperren</span>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2" @click="l.overlays.wishes = !l.overlays.wishes" style="cursor:pointer">
|
||||
<i :class="l.overlays.wishes ? 'fa-solid fa-toggle-on text-primary' : 'fa-solid fa-toggle-off text-muted'"></i>
|
||||
<span class="form-check-label">Zeitwünsche</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import EventCard from '../Calendar/Base/Grid/Line/EventCard.js';
|
||||
import drop from '../../directives/drop.js';
|
||||
|
||||
export default {
|
||||
name: "ParkingSlot",
|
||||
components: {
|
||||
EventCard
|
||||
},
|
||||
directives: {
|
||||
drop
|
||||
},
|
||||
emits: ['update:parkedKeys'],
|
||||
data() {
|
||||
return {
|
||||
parked: [],
|
||||
parkedKeys: new Set()
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
park(evt, items) {
|
||||
const list = Array.isArray(items) ? items : [items];
|
||||
|
||||
const stored = JSON.parse(localStorage.getItem('tempus_parking') || '[]');
|
||||
|
||||
list.forEach(item => {
|
||||
const key = `${item.id}`;
|
||||
if (this.parkedKeys.has(key))
|
||||
return;
|
||||
this.parkedKeys.add(key);
|
||||
stored.push({
|
||||
type: item.type,
|
||||
id: item.id,
|
||||
orig: item.orig
|
||||
});
|
||||
this.parked.push(item);
|
||||
});
|
||||
|
||||
localStorage.setItem('tempus_parking', JSON.stringify(stored));
|
||||
this.$emit('update:parkedKeys', this.parkedKeys);
|
||||
},
|
||||
unpark(event) {
|
||||
const key = `${event.id}`;
|
||||
this.parkedKeys.delete(key);
|
||||
this.parked = this.parked.filter(parkedEvent => parkedEvent.id !== event.id);
|
||||
|
||||
const stored = JSON.parse(localStorage.getItem('tempus_parking') || '[]').filter(parkedEvent => parkedEvent.id !== event.id);
|
||||
localStorage.setItem('tempus_parking', JSON.stringify(stored));
|
||||
this.$emit('update:parkedKeys', this.parkedKeys);
|
||||
},
|
||||
isParked(id) {
|
||||
return this.parkedKeys.has(`${id}`);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const stored = JSON.parse(localStorage.getItem('tempus_parking') || '[]');
|
||||
this.parked = stored;
|
||||
this.parkedKeys = new Set(stored.map(store => `${store.id}`));
|
||||
this.$emit('update:parkedKeys', this.parkedKeys);
|
||||
},
|
||||
template: `
|
||||
<div class="overflow-auto" tabindex="-1">
|
||||
<div
|
||||
id="parkingslot"
|
||||
class="parkingslot"
|
||||
v-drop:move.kalender-collection="(evt, item) => park(evt, item)"
|
||||
>
|
||||
<i class="fa-solid fa-square-parking"></i>
|
||||
<event-card
|
||||
class="parkingevent"
|
||||
v-for="parkedEvent in parked"
|
||||
:key="parkedEvent.id"
|
||||
:event="parkedEvent"
|
||||
parked
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -111,7 +111,7 @@ export function useEventLoader(rangeInterval, getPromiseFunc) {
|
||||
return mergePromiseArr(getPromiseFunc(start, end), result);
|
||||
};
|
||||
|
||||
Vue.watchEffect(() => {
|
||||
const reload = () => {
|
||||
const range = Vue.toValue(rangeInterval);
|
||||
if (!(range instanceof luxon.Interval))
|
||||
return;
|
||||
@@ -132,7 +132,18 @@ export function useEventLoader(rangeInterval, getPromiseFunc) {
|
||||
}
|
||||
})
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
return { events: allEvents, lv }
|
||||
Vue.watchEffect(reload);
|
||||
|
||||
const reset = () => {
|
||||
loading_id = 0;
|
||||
events.value = [];
|
||||
loadingEvents.value = [];
|
||||
eventsLoaded.splice(0, eventsLoaded.length);
|
||||
reload();
|
||||
}
|
||||
|
||||
|
||||
return { events: allEvents, lv, reset }
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
/**
|
||||
* TODO(chris): This needs serious rework!!!
|
||||
*/
|
||||
export default {
|
||||
mounted(el, binding, vnode) {
|
||||
if (binding.arg == 'draggable') {
|
||||
el.addEventListener('update-my-value', evt => {
|
||||
evt.preventDefault();
|
||||
binding.value = evt.detail.item;
|
||||
});
|
||||
el.addEventListener('dragstart', evt => {
|
||||
el.dispatchEvent(new CustomEvent('calendar-dragstart', {
|
||||
cancelable: true,
|
||||
bubbles: true,
|
||||
detail: {
|
||||
item: binding.value,
|
||||
x: evt.offsetX / el.offsetWidth,
|
||||
y: evt.offsetY / el.offsetHeight,
|
||||
originalEvent: evt
|
||||
}
|
||||
}));
|
||||
});
|
||||
el.addEventListener('dragend', evt => {
|
||||
el.dispatchEvent(new CustomEvent('calendar-dragend', {
|
||||
cancelable: true,
|
||||
bubbles: true,
|
||||
detail: {
|
||||
item: binding.value,
|
||||
originalEvent: evt
|
||||
}
|
||||
}));
|
||||
});
|
||||
} else if (binding.arg == 'dropcage') {
|
||||
let hitbox = null;
|
||||
el.addEventListener('dragover', evt => {
|
||||
if (hitbox)
|
||||
return;
|
||||
hitbox = el.getBoundingClientRect();
|
||||
return el.dispatchEvent(new CustomEvent('calendar-dragenter', {
|
||||
detail: { originalEvent: evt }
|
||||
}));
|
||||
});
|
||||
window.addEventListener('dragleave', evt => {
|
||||
if (!hitbox)
|
||||
return;
|
||||
let pos;
|
||||
if (typeof evt.clientX === 'undefined')
|
||||
pos = {
|
||||
x: evt.pageX + document.documentElement.scrollLeft,
|
||||
y: evt.pageY + document.documentElement.scrollTop
|
||||
};
|
||||
else
|
||||
pos = {
|
||||
x: evt.clientX + document.body.scrollLeft + document.documentElement.scrollLeft,
|
||||
y: evt.clientY + document.body.scrollTop + document.documentElement.scrollTop
|
||||
};
|
||||
if (pos.x > hitbox.left + hitbox.width - 1 || pos.x < hitbox.left || pos.y > hitbox.top + hitbox.height - 1 || pos.y < hitbox.top) {
|
||||
hitbox = null;
|
||||
return el.dispatchEvent(new CustomEvent('calendar-dragleave', {
|
||||
detail: { originalEvent: evt }
|
||||
}));
|
||||
}
|
||||
});
|
||||
window.addEventListener('drop', evt => {
|
||||
if (!hitbox)
|
||||
return;
|
||||
|
||||
hitbox = null;
|
||||
return el.dispatchEvent(new CustomEvent('calendar-dragleave', {
|
||||
detail: { originalEvent: evt }
|
||||
}));
|
||||
});
|
||||
} else if (binding.arg == 'dropzone') {
|
||||
el.addEventListener(
|
||||
binding.modifiers.once ? 'dragenter' : 'dragover',
|
||||
evt => {
|
||||
const timestamp = binding.value instanceof Function
|
||||
? binding.value(evt)
|
||||
: binding.value;
|
||||
const detail = timestamp.timestamp ? timestamp : { timestamp };
|
||||
el.dispatchEvent(new CustomEvent('calendar-dragchange', {
|
||||
cancelable: true,
|
||||
bubbles: true,
|
||||
detail
|
||||
}));
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
updated(el, binding, vnode, prevVnode) {
|
||||
if (binding.arg == 'draggable') {
|
||||
el.dispatchEvent(new CustomEvent('update-my-value', {
|
||||
cancelable: true,
|
||||
detail: {
|
||||
item: binding.value
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,8 @@ export default {
|
||||
function onStart(evt) {
|
||||
const value = el.dataset.fhcDraggableValue;
|
||||
if (value) {
|
||||
setTransferData(evt, JSON.parse(value), true);
|
||||
let disableImage = binding.modifiers?.noimage === true;
|
||||
setTransferData(evt, JSON.parse(value), !disableImage);
|
||||
if (el.dataset.fhcEffectAllowed)
|
||||
evt.dataTransfer.effectAllowed = el.dataset.fhcEffectAllowed;
|
||||
|
||||
|
||||
@@ -5,11 +5,13 @@
|
||||
const TYPE_DEFINITION = {
|
||||
lehreinheit: {
|
||||
id: "lehreinheit_id",
|
||||
dragIcon: "fa-solid fa-chalkboard-user",
|
||||
extras: [
|
||||
"stundenblockung"
|
||||
]
|
||||
},
|
||||
kalender: {
|
||||
id: "kalender_id",
|
||||
},
|
||||
vevent: {
|
||||
id: "uid",
|
||||
dragIcon: "fa-solid fa-calendar",
|
||||
@@ -53,12 +55,16 @@ function isValidDragObject(value) {
|
||||
if (!Object.prototype.hasOwnProperty.call(value, 'values'))
|
||||
return false;
|
||||
|
||||
|
||||
if (!VALID_TYPES.includes(value.type.substr(0, value.type.length-11)))
|
||||
return false;
|
||||
|
||||
} else {
|
||||
|
||||
if (!Object.prototype.hasOwnProperty.call(value, 'id'))
|
||||
return false;
|
||||
|
||||
|
||||
if (!VALID_TYPES.includes(value.type))
|
||||
return false;
|
||||
|
||||
@@ -182,10 +188,10 @@ function convertToValidDragObject(data, strict) {
|
||||
const found = Object.entries(TYPE_DEFINITION).find(([ , typedef ]) => {
|
||||
if (!Object.prototype.hasOwnProperty.call(data, typedef.id))
|
||||
return false;
|
||||
if (typedef.extras) {
|
||||
/*if (typedef.extras) {
|
||||
if (!typedef.extras.every(extra => Object.prototype.hasOwnProperty.call(data, extra)))
|
||||
return false;
|
||||
}
|
||||
}*/
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -198,13 +204,16 @@ function convertToValidDragObject(data, strict) {
|
||||
const newData = {};
|
||||
newData.type = type;
|
||||
newData.id = data[typedef.id];
|
||||
if (typedef.extras)
|
||||
typedef.extras.forEach(extra => newData[extra] = data[extra]);
|
||||
|
||||
/*if (typedef.extras)
|
||||
typedef.extras.forEach(extra => newData[extra] = data[extra]);*/
|
||||
|
||||
return newData;
|
||||
}
|
||||
|
||||
function setTransferData(event, validDragObject, setDragImage = false) {
|
||||
|
||||
|
||||
if (setDragImage) {
|
||||
const dragItems = Array.isArray(validDragObject) ? validDragObject : [ validDragObject ];
|
||||
const dragElements = dragItems.map(item => {
|
||||
@@ -246,9 +255,9 @@ function setTransferData(event, validDragObject, setDragImage = false) {
|
||||
});
|
||||
}
|
||||
if (Array.isArray(validDragObject)) {
|
||||
|
||||
return validDragObject.forEach(data => setTransferData(event, data));
|
||||
}
|
||||
|
||||
event.dataTransfer.setData('application/fhc-' + validDragObject.type, JSON.stringify(validDragObject));
|
||||
}
|
||||
|
||||
@@ -267,16 +276,16 @@ function eventHasTypes(event, allowedTypes, strict) {
|
||||
allowedTypes = allowedTypes.map(type => 'application/fhc-' + type);
|
||||
|
||||
const dataTypes = [...event.dataTransfer.types];
|
||||
|
||||
|
||||
// NOTE(chris): if dragging across browsers the dataTransfer object is
|
||||
// set to a default one without data. Since we do not support dragging
|
||||
// across browsers (yet) we return false which will disallow dropping.
|
||||
if (!dataTypes.length)
|
||||
return false;
|
||||
|
||||
|
||||
if (!strict)
|
||||
return allowedTypes.some(type => [...event.dataTransfer.types].includes(type));
|
||||
|
||||
|
||||
return [...event.dataTransfer.types].every(type => allowedTypes.includes(type));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
export function useResizeGhost() {
|
||||
let ghostEl = null;
|
||||
let labelEl = null;
|
||||
|
||||
function create(gridEl, eventEl, edge)
|
||||
{
|
||||
const gridRect = gridEl.getBoundingClientRect();
|
||||
const eventRect = eventEl.getBoundingClientRect();
|
||||
const scrollTop = gridEl.scrollTop;
|
||||
|
||||
const topInGrid = (eventRect.top - gridRect.top) + scrollTop;
|
||||
const leftInGrid = eventRect.left - gridRect.left;
|
||||
|
||||
ghostEl = document.createElement('div');
|
||||
ghostEl.className = 'fhc-event-ghost';
|
||||
ghostEl.style.cssText = `
|
||||
position: absolute;
|
||||
left: ${leftInGrid}px;
|
||||
width: ${eventRect.width}px;
|
||||
top: ${topInGrid}px;
|
||||
height: ${eventRect.height}px;
|
||||
z-index: 9999;
|
||||
pointer-events: none;
|
||||
box-sizing: border-box;
|
||||
border-radius: 6px;
|
||||
outline: 2px dashed currentColor;
|
||||
opacity: 0.9;
|
||||
`;
|
||||
|
||||
labelEl = document.createElement('div');
|
||||
labelEl.className = 'fhc-resize-preview';
|
||||
labelEl.style.cssText = `
|
||||
position: absolute;
|
||||
right: 6px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
background: rgba(0,0,0,0.75);
|
||||
color: white;
|
||||
white-space: nowrap;
|
||||
pointer-events: none;
|
||||
`;
|
||||
|
||||
if (edge === 'start')
|
||||
{
|
||||
labelEl.style.top = '6px';
|
||||
labelEl.style.bottom = 'auto';
|
||||
}
|
||||
else
|
||||
{
|
||||
labelEl.style.top = 'auto';
|
||||
labelEl.style.bottom = '6px';
|
||||
}
|
||||
|
||||
ghostEl.appendChild(labelEl);
|
||||
|
||||
gridEl.style.position = 'relative';
|
||||
gridEl.appendChild(ghostEl);
|
||||
|
||||
return {
|
||||
startTop: topInGrid,
|
||||
startHeight: eventRect.height
|
||||
};
|
||||
}
|
||||
|
||||
function updateLabel(text)
|
||||
{
|
||||
if (labelEl)
|
||||
labelEl.textContent = text;
|
||||
}
|
||||
|
||||
function updatePosition(top, height)
|
||||
{
|
||||
if (!ghostEl)
|
||||
return;
|
||||
if (top !== null)
|
||||
ghostEl.style.top = `${top}px`;
|
||||
if (height !== null)
|
||||
ghostEl.style.height = `${height}px`;
|
||||
}
|
||||
|
||||
function getPosition()
|
||||
{
|
||||
if (!ghostEl)
|
||||
return { top: 0, height: 0 };
|
||||
return {
|
||||
top: parseFloat(ghostEl.style.top),
|
||||
height: parseFloat(ghostEl.style.height)
|
||||
};
|
||||
}
|
||||
|
||||
function remove()
|
||||
{
|
||||
if (ghostEl?.parentNode)
|
||||
ghostEl.parentNode.removeChild(ghostEl);
|
||||
|
||||
ghostEl = null;
|
||||
labelEl = null;
|
||||
}
|
||||
|
||||
return { create, updateLabel, updatePosition, getPosition, remove };
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
import { useResizeGhost } from './ResizeGhost.js';
|
||||
|
||||
const MIN_HEIGHT_PX = 20;
|
||||
const MIN_DURATION_MIN = 5;
|
||||
const SNAP_MINUTES = 5;
|
||||
|
||||
function snapToGrid(minutes, edge)
|
||||
{
|
||||
if (edge === 'start')
|
||||
{
|
||||
return minutes >= 0 ? Math.floor(minutes / SNAP_MINUTES) * SNAP_MINUTES : Math.ceil(minutes / SNAP_MINUTES) * SNAP_MINUTES;
|
||||
}
|
||||
|
||||
return minutes >= 0 ? Math.ceil(minutes / SNAP_MINUTES) * SNAP_MINUTES : Math.floor(minutes / SNAP_MINUTES) * SNAP_MINUTES;
|
||||
}
|
||||
|
||||
function getSnapTimes(timeGrid, dayISO, zoneName)
|
||||
{
|
||||
const parseTime = (time) =>
|
||||
{
|
||||
if (!time)
|
||||
return null;
|
||||
let dt = luxon.DateTime.fromFormat(`${dayISO} ${time}`, 'yyyy-MM-dd HH:mm:ss', { zone: zoneName });
|
||||
|
||||
if (!dt.isValid)
|
||||
dt = luxon.DateTime.fromFormat(`${dayISO} ${time}`, 'yyyy-MM-dd HH:mm', { zone: zoneName });
|
||||
|
||||
return dt.isValid ? dt : null;
|
||||
};
|
||||
|
||||
const startTimes = timeGrid.map(s => parseTime(s?.start)).filter(Boolean);
|
||||
const endTimes = timeGrid.map(s => parseTime(s?.end)).filter(Boolean);
|
||||
|
||||
const sort = arr => arr.sort((a, b) => a.toMillis() - b.toMillis());
|
||||
|
||||
return {
|
||||
start: sort(startTimes),
|
||||
end: sort(endTimes),
|
||||
};
|
||||
}
|
||||
|
||||
function calculateNewTimes(activeResize, ghostPosition)
|
||||
{
|
||||
const { edge, event, timeGrid, startTop, startHeight } = activeResize;
|
||||
const { start, end } = event;
|
||||
|
||||
const durationMinutes = end.diff(start, 'minutes').minutes;
|
||||
if (!durationMinutes || durationMinutes <= 0)
|
||||
return null;
|
||||
|
||||
const pxPerMinute = startHeight / durationMinutes;
|
||||
|
||||
let draggedPx = 0;
|
||||
if (edge === 'end')
|
||||
draggedPx = ghostPosition.height - startHeight;
|
||||
if (edge === 'start')
|
||||
draggedPx = ghostPosition.top - startTop;
|
||||
|
||||
const draggedMinutes = snapToGrid(draggedPx / pxPerMinute, edge);
|
||||
|
||||
let newStart = start;
|
||||
let newEnd = end;
|
||||
|
||||
if (edge === 'start')
|
||||
newStart = start.plus({ minutes: draggedMinutes });
|
||||
if (edge === 'end')
|
||||
newEnd = end.plus({ minutes: draggedMinutes });
|
||||
|
||||
if (Array.isArray(timeGrid) && timeGrid.length)
|
||||
{
|
||||
const snapTimes = getSnapTimes(timeGrid, start.toISODate(), start.zoneName);
|
||||
|
||||
if (edge === 'start')
|
||||
{
|
||||
const targets = snapTimes.start;
|
||||
newStart = [...targets].reverse().find(t => t <= newStart) || targets[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
const targets = snapTimes.end;
|
||||
newEnd = targets.find(t => t >= newEnd) || targets[targets.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
return { newStart, newEnd };
|
||||
}
|
||||
|
||||
|
||||
export function useResizeHandler() {
|
||||
const ghost = useResizeGhost();
|
||||
|
||||
let activeResize = null;
|
||||
|
||||
function getPointerYInGrid(evt)
|
||||
{
|
||||
const gridRect = activeResize.gridEl.getBoundingClientRect();
|
||||
return (evt.clientY - gridRect.top) + activeResize.gridEl.scrollTop;
|
||||
}
|
||||
|
||||
function updateGhostLabel()
|
||||
{
|
||||
const result = calculateNewTimes(activeResize, ghost.getPosition());
|
||||
if (!result)
|
||||
return;
|
||||
ghost.updateLabel(`${result.newStart.toFormat('HH:mm')}–${result.newEnd.toFormat('HH:mm')}`);
|
||||
}
|
||||
|
||||
function onPointerMove(evt)
|
||||
{
|
||||
if (!activeResize || evt.pointerId !== activeResize.pointerId)
|
||||
return;
|
||||
evt.preventDefault();
|
||||
|
||||
const maxBottom = activeResize.gridEl.scrollHeight;
|
||||
const pointerY = getPointerYInGrid(evt);
|
||||
const draggedPx = pointerY - activeResize.dragStartY;
|
||||
|
||||
if (activeResize.edge === 'end')
|
||||
{
|
||||
let newHeight = Math.max(MIN_HEIGHT_PX, activeResize.startHeight + draggedPx);
|
||||
if (activeResize.startTop + newHeight > maxBottom)
|
||||
newHeight = maxBottom - activeResize.startTop;
|
||||
|
||||
ghost.updatePosition(null, newHeight);
|
||||
}
|
||||
else if (activeResize.edge === 'start')
|
||||
{
|
||||
let newTop = activeResize.startTop + draggedPx;
|
||||
let newHeight = activeResize.startHeight - draggedPx;
|
||||
|
||||
if (newTop < 0)
|
||||
{
|
||||
newHeight -= (0 - newTop);
|
||||
newTop = 0;
|
||||
}
|
||||
|
||||
if (newHeight < MIN_HEIGHT_PX)
|
||||
{
|
||||
newTop = (activeResize.startTop + activeResize.startHeight) - MIN_HEIGHT_PX;
|
||||
newHeight = MIN_HEIGHT_PX;
|
||||
}
|
||||
|
||||
ghost.updatePosition(newTop, newHeight);
|
||||
}
|
||||
|
||||
updateGhostLabel();
|
||||
}
|
||||
|
||||
function onPointerUp(evt)
|
||||
{
|
||||
if (!activeResize || evt.pointerId !== activeResize.pointerId)
|
||||
return;
|
||||
|
||||
window.removeEventListener('pointermove', onPointerMove);
|
||||
window.removeEventListener('pointerup', onPointerUp);
|
||||
|
||||
if (activeResize.eventEl)
|
||||
activeResize.eventEl.style.opacity = activeResize.originalOpacity ?? '';
|
||||
|
||||
const result = calculateNewTimes(activeResize, ghost.getPosition());
|
||||
|
||||
ghost.remove();
|
||||
|
||||
if (result)
|
||||
{
|
||||
if ((activeResize.event.start.toISO() !== result.newStart.toISO()) || (activeResize.event.end.toISO() !== result.newEnd.toISO()))
|
||||
{
|
||||
activeResize.onEnd({
|
||||
event: activeResize.event,
|
||||
newStart: result.newStart.toISO(),
|
||||
newEnd: result.newEnd.toISO()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
activeResize = null;
|
||||
}
|
||||
|
||||
function startResize(edge, evt, { el, gridEl, event, timeGrid, onEnd })
|
||||
{
|
||||
const { startTop, startHeight } = ghost.create(gridEl, el, edge);
|
||||
|
||||
activeResize = {
|
||||
edge,
|
||||
pointerId: evt.pointerId,
|
||||
eventEl: el,
|
||||
gridEl,
|
||||
event,
|
||||
timeGrid,
|
||||
onEnd,
|
||||
dragStartY: (evt.clientY - gridEl.getBoundingClientRect().top) + gridEl.scrollTop,
|
||||
startTop,
|
||||
startHeight,
|
||||
originalOpacity: el.style.opacity,
|
||||
};
|
||||
|
||||
el.style.opacity = '0.35';
|
||||
ghost.updateLabel(`${event.start.toFormat('HH:mm')}–${event.end.toFormat('HH:mm')}`);
|
||||
|
||||
evt.currentTarget.setPointerCapture(evt.pointerId);
|
||||
window.addEventListener('pointermove', onPointerMove, { passive: false });
|
||||
window.addEventListener('pointerup', onPointerUp, { passive: false });
|
||||
}
|
||||
|
||||
function cleanup()
|
||||
{
|
||||
if (!activeResize)
|
||||
return;
|
||||
|
||||
window.removeEventListener('pointermove', onPointerMove);
|
||||
window.removeEventListener('pointerup', onPointerUp);
|
||||
ghost.remove();
|
||||
activeResize = null;
|
||||
}
|
||||
|
||||
return { startResize, cleanup };
|
||||
}
|
||||
Reference in New Issue
Block a user