mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-10-18 17:59:35 +00:00
Conflicts resolved
This commit is contained in:
@@ -135,6 +135,8 @@ $route['lvplan/html/detail'] = 'LvPlanHTMLDetail';
|
||||
// load routes from extensions, also look for environment-specific configs
|
||||
$subdirs = ['application/config/extensions', 'application/config/' . ENVIRONMENT . '/extensions'];
|
||||
|
||||
$route['api/frontend/v1/tempus/Tags/getTagsByCalendar/(:num)'] = 'api/frontend/v1/tempus/Tags/getTagsByCalendar/$1';
|
||||
|
||||
foreach($subdirs as $subdir)
|
||||
{
|
||||
if(is_dir($subdir))
|
||||
|
||||
@@ -2,4 +2,11 @@
|
||||
|
||||
$config['send_update_mails'] = false;
|
||||
$config['calendar_start'] = 7;
|
||||
$config['calendar_end'] = 23;
|
||||
$config['calendar_end'] = 23;
|
||||
|
||||
|
||||
$config['tempus_tags'] = [
|
||||
'prioone' => ['readonly' => false],
|
||||
'priotwo' => ['readonly' => true],
|
||||
'hinweis' => ['readonly' => false],
|
||||
];
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
if (!defined('BASEPATH'))
|
||||
exit('No direct script access allowed');
|
||||
|
||||
class Tags extends Tag_Controller
|
||||
{
|
||||
const BERECHTIGUNG_KURZBZ = ['admin:rw', 'assistenz:rw'];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct([
|
||||
'getTag' => self::BERECHTIGUNG_KURZBZ,
|
||||
'getTags' => self::BERECHTIGUNG_KURZBZ,
|
||||
'getTagsByCalendar' => self::BERECHTIGUNG_KURZBZ,
|
||||
'addTag' => self::BERECHTIGUNG_KURZBZ,
|
||||
'updateTag' => self::BERECHTIGUNG_KURZBZ,
|
||||
'doneTag' => self::BERECHTIGUNG_KURZBZ,
|
||||
'deleteTag' => self::BERECHTIGUNG_KURZBZ
|
||||
]);
|
||||
|
||||
$this->config->load('tempus');
|
||||
}
|
||||
|
||||
public function getTag($readonly_tags = null)
|
||||
{
|
||||
parent::getTag($this->config->item('tempus_tags'));
|
||||
}
|
||||
public function getTags($tags = null)
|
||||
{
|
||||
parent::getTags($this->config->item('tempus_tags'));
|
||||
}
|
||||
public function getTagsByCalendar($eindeutige_kalender_gruppen_id = null)
|
||||
{
|
||||
parent::getTagsByAssignmentTypeValue('eindeutige_kalender_gruppen_id', $eindeutige_kalender_gruppen_id, $this->config->item('tempus_tags'));
|
||||
}
|
||||
public function addTag($withZuordnung = true, $updatable_tags = null)
|
||||
{
|
||||
parent::addTag(true, $this->config->item('tempus_tags'));
|
||||
}
|
||||
public function updateTag($updatable_tags = null)
|
||||
{
|
||||
parent::updateTag($this->config->item('tempus_tags'));
|
||||
}
|
||||
public function deleteTag($withZuordnung = true, $updatable_tags = null)
|
||||
{
|
||||
parent::deleteTag(true, $this->config->item('tempus_tags'));
|
||||
}
|
||||
public function doneTag($updatable_tags = null)
|
||||
{
|
||||
parent::doneTag($this->config->item('tempus_tags'));
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,36 @@ class MigrateKalender extends CLI_Controller
|
||||
/**
|
||||
* Everything has a beginning
|
||||
*/
|
||||
public function refreshTablesAndFullMigrateOfStundenplanReservierung($von, $bis = null, $studiengang_kz = null, $ort_kurzbz = null)
|
||||
{
|
||||
echo "Refreshing calendar related tables...\n";
|
||||
$this->resetBeforeNewStundenplanReservierungImport();
|
||||
echo "Calendar related tables refreshed successfully.\n\n";
|
||||
|
||||
echo "Starting full migration of Stundenplan and Reservierung from $von to " . ($bis ?? '2100-12-31') . "...\n";
|
||||
$this->fullMigrateOfStundenplanReservierung($von, $bis, $studiengang_kz, $ort_kurzbz);
|
||||
echo "Full migration of Stundenplan and Reservierung completed successfully.\n";
|
||||
}
|
||||
|
||||
public function fullMigrateOfStundenplanReservierung($von, $bis = null, $studiengang_kz = null, $ort_kurzbz = null)
|
||||
{
|
||||
echo "Starting migration of Stundenplan from $von to " . ($bis ?? '2100-12-31') . "...\n";
|
||||
$this->migrateStundenplan($von, $bis, $studiengang_kz);
|
||||
echo "Stundenplan migration completed successfully.\n\n";
|
||||
|
||||
echo "Starting migration of Reservierung from $von to " . ($bis ?? '2100-12-31') . "...\n";
|
||||
$this->migrateReservierung($von, $bis, $ort_kurzbz);
|
||||
echo "Reservierung migration completed successfully.\n\n";
|
||||
|
||||
echo "Starting migration of Stundenplan Betriebsmittel entries...\n";
|
||||
$this->migrateStundenplanBetriebsmittelEntries();
|
||||
echo "Stundenplan Betriebsmittel entries migration completed successfully.\n\n";
|
||||
|
||||
echo "Starting to add title values as tags to calendar entries...\n";
|
||||
$this->addTitleValuesAsTagsToKalenderEntries();
|
||||
echo "Adding title values as tags to calendar entries completed successfully.\n\n";
|
||||
}
|
||||
|
||||
public function migrateStundenplan($von, $bis = null, $studiengang_kz = null)
|
||||
{
|
||||
if (is_null($von))
|
||||
@@ -268,8 +298,9 @@ class MigrateKalender extends CLI_Controller
|
||||
* Removes calendar entries created by the Stundenplan and Reservierung migrations,
|
||||
* so that both imports can be started from scratch.
|
||||
*/
|
||||
public function resetBeforeNewStundenplandevImport()
|
||||
public function resetBeforeNewStundenplanReservierungImport()
|
||||
{
|
||||
|
||||
$db = new DB_Model();
|
||||
|
||||
$db->db->trans_start();
|
||||
@@ -305,6 +336,28 @@ class MigrateKalender extends CLI_Controller
|
||||
WHERE typ IN ('lehreinheit', 'reservierung')
|
||||
)");
|
||||
|
||||
$db->db->query("DELETE FROM public.tbl_notiz
|
||||
WHERE notiz_id IN (
|
||||
SELECT notiz_id
|
||||
FROM public.tbl_notizzuordnung
|
||||
WHERE eindeutige_kalender_gruppen_id IN (
|
||||
SELECT eindeutige_gruppen_id
|
||||
FROM lehre.tbl_kalender
|
||||
)
|
||||
)");
|
||||
|
||||
$db->db->query("DELETE FROM public.tbl_notizzuordnung
|
||||
WHERE eindeutige_kalender_gruppen_id IN (
|
||||
SELECT eindeutige_gruppen_id
|
||||
FROM lehre.tbl_kalender
|
||||
)");
|
||||
|
||||
$db->db->query("DELETE FROM public.tbl_betriebsmittel_kalender
|
||||
WHERE eindeutige_kalender_gruppen_id IN (
|
||||
SELECT eindeutige_gruppen_id
|
||||
FROM lehre.tbl_kalender
|
||||
)");
|
||||
|
||||
$db->db->query("DELETE FROM lehre.tbl_kalender
|
||||
WHERE typ IN ('lehreinheit', 'reservierung')");
|
||||
|
||||
@@ -371,6 +424,45 @@ class MigrateKalender extends CLI_Controller
|
||||
$dbModel->db->query($query);
|
||||
}
|
||||
|
||||
public function addTitleValuesAsTagsToKalenderEntries() {
|
||||
$dbModel = new DB_Model();
|
||||
|
||||
|
||||
$query = "SELECT
|
||||
k.eindeutige_gruppen_id,
|
||||
ARRAY_AGG(DISTINCT(spd.titel) ORDER BY spd.titel) AS titel,
|
||||
MAX(spd.updateamum) AS updateamum
|
||||
FROM lehre.tbl_stundenplandev spd
|
||||
JOIN sync.tbl_stundenplandev_kalender spdk ON spd.stundenplandev_id = spdk.stundenplandev_id
|
||||
JOIN lehre.tbl_kalender k ON k.kalender_id = spdk.kalender_id
|
||||
WHERE k.typ = 'lehreinheit' AND k.eindeutige_gruppen_id IS NOT NULL AND spd.titel IS NOT NULL
|
||||
GROUP BY k.eindeutige_gruppen_id";
|
||||
|
||||
$result = $dbModel->execReadOnlyQuery($query);
|
||||
|
||||
if (isError($result)) {
|
||||
error("Error while fetching title values for adding as tags: " . getError($result));
|
||||
return;
|
||||
}
|
||||
if (!hasData($result)) {
|
||||
error("No title values found for adding as tags.");
|
||||
return;
|
||||
}
|
||||
|
||||
$data = getData($result);
|
||||
|
||||
foreach($data as $block) {
|
||||
$eindeutige_gruppen_id = $block->eindeutige_gruppen_id;
|
||||
$notizText = implode(' - ', array_filter($block->titel, function($titel) {
|
||||
return !empty(trim($titel));
|
||||
}));
|
||||
|
||||
if (!empty($notizText)) {
|
||||
$this->addTag($eindeutige_gruppen_id, $notizText, $block->updateamum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function setKalendarEntriesGroupIDs() {
|
||||
$dbModel = new DB_Model();
|
||||
|
||||
@@ -645,4 +737,129 @@ class MigrateKalender extends CLI_Controller
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function addTag($eindeutige_kalender_gruppen_id, $notizText, $lastUpdatedInOldSystem): bool
|
||||
{
|
||||
$notizType = 'hinweis';
|
||||
$insertvonMockUser = 'oldToNewTempusMigration';
|
||||
|
||||
if (is_null($eindeutige_kalender_gruppen_id) || empty($eindeutige_kalender_gruppen_id)) {
|
||||
var_dump("Error: eindeutige_kalender_gruppen_id is null or empty.");
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->load->model('person/Notiz_model', 'NotizModel');
|
||||
$this->load->model('system/Notiztyp_model', 'NotiztypModel');
|
||||
$this->load->model('person/Notizzuordnung_model', 'NotizzuordnungModel');
|
||||
|
||||
$checkTyp = $this->NotiztypModel->loadWhere(array('typ_kurzbz' => $notizType));
|
||||
|
||||
if (isError($checkTyp))
|
||||
{
|
||||
error("Error occurred while checking Notiztyp: " . $checkTyp->message);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!hasData($checkTyp))
|
||||
{
|
||||
error("Notiztyp 'hinweis' does not exist. Please create it before adding tags.");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
$this->NotizzuordnungModel->addJoin('tbl_notiz', 'tbl_notiz.notiz_id = tbl_notizzuordnung.notiz_id', 'LEFT');
|
||||
$this->NotizzuordnungModel->db->where('tbl_notiz.typ', $notizType);
|
||||
$this->NotizzuordnungModel->db->where('tbl_notiz.insertvon', $insertvonMockUser);
|
||||
$oldNotizZuordnung = $this->NotizzuordnungModel->loadWhere(array('eindeutige_kalender_gruppen_id' => $eindeutige_kalender_gruppen_id));
|
||||
if (isError($oldNotizZuordnung))
|
||||
{
|
||||
error("Error occurred while checking Notizzuordnung: " . $oldNotizZuordnung->message);
|
||||
return false;
|
||||
}
|
||||
if (hasData($oldNotizZuordnung))
|
||||
{
|
||||
return $this->updateTag($eindeutige_kalender_gruppen_id, $notizText, $lastUpdatedInOldSystem);
|
||||
}
|
||||
|
||||
|
||||
$insertResult = $this->NotizModel->insert(array(
|
||||
'titel' => 'TAG',
|
||||
'text' => $notizText,
|
||||
'verfasser_uid' => null,
|
||||
'erledigt' => false,
|
||||
'insertamum' => date('Y-m-d H:i:s'),
|
||||
'insertvon' => $insertvonMockUser,
|
||||
'typ' => $notizType
|
||||
));
|
||||
|
||||
if (isError($insertResult))
|
||||
{
|
||||
error("Error occurred while inserting Notiz: " . $insertResult->message);
|
||||
return false;
|
||||
}
|
||||
|
||||
$insertZuordnung = $this->NotizzuordnungModel->insert(array(
|
||||
'notiz_id' => $insertResult->retval,
|
||||
'eindeutige_kalender_gruppen_id' => $eindeutige_kalender_gruppen_id
|
||||
));
|
||||
|
||||
if (isError($insertZuordnung))
|
||||
{
|
||||
error("Error occurred while inserting Notizzuordnung: " . $insertZuordnung->message);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function updateTag($eindeutige_kalender_gruppen_id, $notizText, $lastUpdatedInOldSystem)
|
||||
{
|
||||
$notizType = 'hinweis';
|
||||
$insertvonMockUser = 'oldToNewTempusMigration';
|
||||
|
||||
$this->NotizzuordnungModel->addJoin('tbl_notiz', 'tbl_notiz.notiz_id = tbl_notizzuordnung.notiz_id', 'LEFT');
|
||||
$this->NotizzuordnungModel->db->where('tbl_notiz.typ', $notizType);
|
||||
$this->NotizzuordnungModel->db->where('tbl_notiz.insertvon', $insertvonMockUser);
|
||||
$notizZuordnungRes = $this->NotizzuordnungModel->loadWhere(array('eindeutige_kalender_gruppen_id' => $eindeutige_kalender_gruppen_id));
|
||||
if (isError($notizZuordnungRes))
|
||||
{
|
||||
error("Error occurred while loading Notizzuordnung: " . $notizZuordnungRes->message);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
$tag = $this->NotizModel->loadWhere(array('notiz_id' => $notizZuordnungRes->retval[0]->notiz_id));
|
||||
if (isError($tag))
|
||||
{
|
||||
error("Error occurred while loading Notiz: " . $tag->message);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!hasData($tag))
|
||||
{
|
||||
error("Error occurred while loading Notiz: " . $tag->message);
|
||||
return false;
|
||||
}
|
||||
|
||||
$adjustedLastUpdatedInOldSystem = date('Y-m-d H:i:s', strtotime($lastUpdatedInOldSystem) + 5);
|
||||
if ($tag->retval[0]->updateamum >= $adjustedLastUpdatedInOldSystem)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
$updateData = $this->NotizModel->update(array('notiz_id' => $tag->retval[0]->notiz_id),
|
||||
array('text' => $notizText,
|
||||
'updateamum' => date('Y-m-d H:i:s'),
|
||||
'updatevon' => $insertvonMockUser,
|
||||
'bearbeiter_uid' => null,
|
||||
)
|
||||
);
|
||||
if (isError($updateData))
|
||||
{
|
||||
error("Error occurred while updating Notiz: " . $updateData->message);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ class Tag_Controller extends FHCAPI_Controller
|
||||
public function getTag($readonly_tags = null)
|
||||
{
|
||||
$language = $this->_getLanguageIndex();
|
||||
$index_bezeichnung_mehrsprachig = $language - 1;
|
||||
|
||||
$id = $this->input->get('id');
|
||||
|
||||
if (is_array($readonly_tags) && !isEmptyArray($readonly_tags))
|
||||
@@ -62,7 +64,7 @@ class Tag_Controller extends FHCAPI_Controller
|
||||
$this->NotizModel->addSelect(
|
||||
"tbl_notiz.titel,
|
||||
tbl_notiz.text,
|
||||
array_to_json(bezeichnung_mehrsprachig::varchar[])->>". $language. " as bezeichnung,
|
||||
array_to_json(bezeichnung_mehrsprachig::varchar[])->>". $index_bezeichnung_mehrsprachig. " as bezeichnung,
|
||||
tbl_notiz.notiz_id,
|
||||
tbl_notiz_typ.style,
|
||||
tbl_notiz.erledigt as done,
|
||||
@@ -87,9 +89,12 @@ class Tag_Controller extends FHCAPI_Controller
|
||||
|
||||
public function getTags($tags = null)
|
||||
{
|
||||
$language = $this->_getLanguageIndex();
|
||||
$index_bezeichnung_mehrsprachig = $language - 1;
|
||||
|
||||
$this->NotiztypModel->addSelect(
|
||||
'typ_kurzbz as tag_typ_kurzbz,
|
||||
array_to_json(bezeichnung_mehrsprachig::varchar[])->>0 as bezeichnung,
|
||||
array_to_json(bezeichnung_mehrsprachig::varchar[])->>'. $index_bezeichnung_mehrsprachig. ' as bezeichnung,
|
||||
style,
|
||||
beschreibung,
|
||||
tag
|
||||
@@ -106,7 +111,36 @@ class Tag_Controller extends FHCAPI_Controller
|
||||
$notiztypen = $this->NotiztypModel->loadWhere(array('aktiv' => true));
|
||||
$this->terminateWithSuccess(hasData($notiztypen) ? getData($notiztypen) : array());
|
||||
}
|
||||
public function getTagsByAssignmentTypeValue($zuordnung_typ, $zuordnung_id, $tags = null)
|
||||
{
|
||||
$language = $this->_getLanguageIndex();
|
||||
$index_bezeichnung_mehrsprachig = $language - 1;
|
||||
|
||||
$this->NotizzuordnungModel->addSelect(
|
||||
'tbl_notizzuordnung.notiz_id as notiz_id,
|
||||
typ_kurzbz as tag_typ_kurzbz,
|
||||
array_to_json(bezeichnung_mehrsprachig::varchar[])->>'. $index_bezeichnung_mehrsprachig. ' as bezeichnung,
|
||||
style,
|
||||
beschreibung,
|
||||
tag,
|
||||
tbl_notiz.erledigt as done
|
||||
'
|
||||
);
|
||||
|
||||
if (is_array($tags) && !isEmptyArray($tags))
|
||||
{
|
||||
$tags = $this->_filterTag($tags, false);
|
||||
$this->NotizzuordnungModel->db->where_in('tbl_notiz_typ.typ_kurzbz', $tags);
|
||||
}
|
||||
|
||||
$this->NotizzuordnungModel->addJoin('public.tbl_notiz', 'public.tbl_notizzuordnung.notiz_id = public.tbl_notiz.notiz_id');
|
||||
$this->NotizzuordnungModel->addJoin('public.tbl_notiz_typ', 'public.tbl_notiz.typ = public.tbl_notiz_typ.typ_kurzbz');
|
||||
|
||||
$this->NotizzuordnungModel->addOrder('prioritaet');
|
||||
|
||||
$notiztypen = $this->NotizzuordnungModel->loadWhere(array('aktiv' => true, $zuordnung_typ => $zuordnung_id));
|
||||
$this->terminateWithSuccess(hasData($notiztypen) ? getData($notiztypen) : array());
|
||||
}
|
||||
public function addTag($withZuordnung = true, $updatable_tags = null)
|
||||
{
|
||||
$postData = $this->getPostJson();
|
||||
@@ -126,7 +160,7 @@ class Tag_Controller extends FHCAPI_Controller
|
||||
if (!in_array($postData->tag_typ_kurzbz, $tags))
|
||||
$this->terminateWithError($this->p->t('ui', 'keineBerechtigung'));
|
||||
}
|
||||
|
||||
|
||||
if ($withZuordnung)
|
||||
{
|
||||
$return = array();
|
||||
@@ -135,7 +169,7 @@ class Tag_Controller extends FHCAPI_Controller
|
||||
$this->terminateWithError('Error occurred', self::ERROR_TYPE_GENERAL);
|
||||
|
||||
$values = array_unique($postData->values);
|
||||
|
||||
|
||||
foreach ($values as $value)
|
||||
{
|
||||
$insertResult = $this->addNotiz($postData);
|
||||
|
||||
@@ -169,6 +169,69 @@ class KalenderLib
|
||||
);
|
||||
$this->_ci->KalenderModel->addJoin('public.tbl_studiengang le_studiengang', 'le_lehrverband.studiengang_kz = le_studiengang.studiengang_kz', 'LEFT');
|
||||
|
||||
// Include tags in the query
|
||||
$this->_ci->load->config('tempus');
|
||||
$tags = $this->_ci->config->item('tempus_tags');
|
||||
|
||||
$whereTags = '';
|
||||
if (is_array($tags) && !isEmptyArray($tags)) {
|
||||
$tags = array_keys($tags);
|
||||
|
||||
foreach ($tags as $key => $tag) {
|
||||
$tags[$key] = $this->_ci->KalenderModel->escape($tag);
|
||||
}
|
||||
$whereTags = " AND nt.typ_kurzbz IN (" . implode(",", $tags) . ")";
|
||||
}
|
||||
$subQueryTag = "(
|
||||
SELECT
|
||||
tag.eindeutige_kalender_gruppen_id,
|
||||
COALESCE(json_agg(tag ORDER BY tag.done, tag.prioritaet), '[]'::json) AS tags
|
||||
FROM (
|
||||
SELECT DISTINCT ON (n.notiz_id)
|
||||
n.notiz_id AS id,
|
||||
nt.typ_kurzbz,
|
||||
array_to_json(nt.bezeichnung_mehrsprachig)->>0 AS beschreibung,
|
||||
n.text AS notiz,
|
||||
nt.style,
|
||||
n.erledigt AS done,
|
||||
nt.prioritaet AS prioritaet,
|
||||
nz.eindeutige_kalender_gruppen_id
|
||||
FROM public.tbl_notizzuordnung AS nz
|
||||
JOIN public.tbl_notiz AS n ON nz.notiz_id = n.notiz_id
|
||||
JOIN public.tbl_notiz_typ AS nt ON n.typ = nt.typ_kurzbz " . $whereTags . "
|
||||
) AS tag
|
||||
GROUP BY tag.eindeutige_kalender_gruppen_id
|
||||
) AS tag_data_agg";
|
||||
|
||||
$this->_ci->KalenderModel->addJoin($subQueryTag, 'tag_data_agg.eindeutige_kalender_gruppen_id = tbl_kalender.eindeutige_gruppen_id', 'LEFT');
|
||||
// End of tags inclusion
|
||||
|
||||
// Include assigned operational resources in the query
|
||||
$subQueryResource = "(
|
||||
SELECT
|
||||
resource.eindeutige_kalender_gruppen_id,
|
||||
COALESCE(json_agg(resource ORDER BY resource.beschreibung), '[]'::json) AS resources
|
||||
FROM (
|
||||
SELECT
|
||||
betriebsmittel_kalender.*,
|
||||
betriebsmittel.beschreibung,
|
||||
betriebsmittel.verplanen
|
||||
FROM lehre.tbl_betriebsmittel_kalender AS betriebsmittel_kalender
|
||||
JOIN wawi.tbl_betriebsmittel AS betriebsmittel
|
||||
ON betriebsmittel.betriebsmittel_id = betriebsmittel_kalender.betriebsmittel_id
|
||||
WHERE betriebsmittel.verplanen = TRUE
|
||||
) AS resource
|
||||
GROUP BY resource.eindeutige_kalender_gruppen_id
|
||||
) AS resource_data_agg";
|
||||
|
||||
$this->_ci->KalenderModel->addJoin(
|
||||
$subQueryResource,
|
||||
'resource_data_agg.eindeutige_kalender_gruppen_id = tbl_kalender.eindeutige_gruppen_id',
|
||||
'LEFT'
|
||||
);
|
||||
// End of operational resources inclusion
|
||||
|
||||
|
||||
$this->_ci->KalenderModel->db->where('tbl_kalender.von >=', $start_date);
|
||||
$this->_ci->KalenderModel->db->where('tbl_kalender.bis <', $end_date);
|
||||
}
|
||||
@@ -190,6 +253,8 @@ class KalenderLib
|
||||
{
|
||||
$von = new DateTime($row->von);
|
||||
$bis = new DateTime($row->bis);
|
||||
$resources = isset($row->resources) ? $row->resources : [];
|
||||
$parsedResources = is_string($resources) ? json_decode($resources, true) : $resources;
|
||||
|
||||
$events[$id] = (object) [
|
||||
'kalender_id' => $id,
|
||||
@@ -235,6 +300,8 @@ class KalenderLib
|
||||
'collisions' => false,
|
||||
'has_assigned_resources' => isset($row->has_assigned_resources) ? $row->has_assigned_resources : false,
|
||||
'updateamum' => isset($row->updateamum) ? $row->updateamum : null,
|
||||
'resources' => is_array($parsedResources) ? $parsedResources : [],
|
||||
'tags' => isset($row->tags) ? $row->tags : [],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -566,15 +633,6 @@ class KalenderLib
|
||||
|
||||
$this->_ci->KalenderModel->db->where_not_in('status_kurzbz', array('deleted'));
|
||||
|
||||
$this->_ci->KalenderModel->addSelect([
|
||||
"(
|
||||
SELECT COUNT(*) FROM lehre.tbl_betriebsmittel_kalender
|
||||
WHERE tbl_betriebsmittel_kalender.eindeutige_kalender_gruppen_id = tbl_kalender.eindeutige_gruppen_id
|
||||
) AS has_assigned_resources"
|
||||
]);
|
||||
|
||||
$this->_ci->KalenderModel->addOrder('tbl_kalender.eindeutige_gruppen_id', 'DESC');
|
||||
|
||||
$data = $this->_ci->KalenderModel->load();
|
||||
|
||||
return $this->_mapEvents($data);
|
||||
@@ -1383,16 +1441,6 @@ class KalenderLib
|
||||
}
|
||||
}
|
||||
|
||||
/* foreach ($specialGroups as $group)
|
||||
{
|
||||
$this->_addSpecialGroupToEvent($kalender_id, $group['gruppe_kurzbz'], $group['rolle']);
|
||||
}
|
||||
|
||||
foreach ($groups as $group)
|
||||
{
|
||||
$this->_addGroupToEvent($kalender_id, $group['stg_kz'], $group['semester'], $group['verband'], $group['gruppe'], $group['rolle']);
|
||||
}*/
|
||||
|
||||
if (isSuccess($kalendereventresult) && !is_null($ort_kurzbz))
|
||||
{
|
||||
$ortresult = $this->_addKalenderOrt($kalender_id, $ort_kurzbz);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
'tabulator5' => true,
|
||||
'vuedatepicker11' => true,
|
||||
'momentjs2' => true,
|
||||
'tags' => true,
|
||||
'phrases' => array(
|
||||
'global',
|
||||
'ui',
|
||||
|
||||
@@ -86,6 +86,9 @@
|
||||
pointer-events: none;
|
||||
background-color: transparent !important;
|
||||
border: none !important;
|
||||
width: fit-content;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.fhc-resize-bar--top {
|
||||
|
||||
@@ -17,6 +17,13 @@
|
||||
transform: scale(1.15);
|
||||
}
|
||||
|
||||
/* add prop that disasbles hover and cursor*/
|
||||
.tag.disabled:hover
|
||||
{
|
||||
transform: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.tag_rot {
|
||||
background-color: #ff0000ff;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
export default {
|
||||
|
||||
getTag(data)
|
||||
{
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/tempus/Tags/getTag',
|
||||
params: data
|
||||
};
|
||||
},
|
||||
|
||||
getTags(data)
|
||||
{
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/tempus/Tags/getTags'
|
||||
};
|
||||
},
|
||||
getTagsByCalendar(calenderGroupId)
|
||||
{
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/tempus/Tags/getTagsByCalendar/' + calenderGroupId,
|
||||
};
|
||||
},
|
||||
addTag(data)
|
||||
{
|
||||
return {
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/tempus/Tags/addTag',
|
||||
params: data
|
||||
};
|
||||
},
|
||||
updateTag(data)
|
||||
{
|
||||
return {
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/tempus/Tags/updateTag',
|
||||
params: data
|
||||
};
|
||||
},
|
||||
doneTag(data)
|
||||
{
|
||||
return {
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/tempus/Tags/doneTag',
|
||||
params: data
|
||||
};
|
||||
},
|
||||
|
||||
deleteTag(data)
|
||||
{
|
||||
return {
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/tempus/Tags/deleteTag',
|
||||
params: data
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -14,6 +14,10 @@ export default {
|
||||
'deleted',
|
||||
],
|
||||
props: {
|
||||
isListItemShown: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
endpoint: {
|
||||
type: Object,
|
||||
required: true
|
||||
@@ -71,7 +75,7 @@ export default {
|
||||
'id': tag_id
|
||||
};
|
||||
|
||||
this.$api.call(this.endpoint.getTag(getData))
|
||||
await this.$api.call(this.endpoint.getTag(getData))
|
||||
.then(result => result.data)
|
||||
.then(result => this.openModal(result))
|
||||
},
|
||||
@@ -119,7 +123,7 @@ export default {
|
||||
{
|
||||
postData.id = this.selectedTagId;
|
||||
this.tagData.id = this.selectedTagId;
|
||||
this.$api.call(this.endpoint.updateTag(postData));
|
||||
await this.$api.call(this.endpoint.updateTag(postData));
|
||||
this.$emit("updated", this.tagData);
|
||||
this.$refs.tagModal.hide();
|
||||
}
|
||||
@@ -131,7 +135,7 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$api.call(this.endpoint.addTag(postData))
|
||||
await this.$api.call(this.endpoint.addTag(postData))
|
||||
.then(response => response.data)
|
||||
.then(response => {
|
||||
if (typeof response === 'number') {
|
||||
@@ -158,7 +162,7 @@ export default {
|
||||
done: !this.tagData.done,
|
||||
notiz: this.tagData.notiz,
|
||||
}
|
||||
this.$api.call(this.endpoint.doneTag(postData))
|
||||
await this.$api.call(this.endpoint.doneTag(postData))
|
||||
this.$emit("updated", this.tagData);
|
||||
this.$refs.tagModal.hide();
|
||||
},
|
||||
@@ -167,7 +171,7 @@ export default {
|
||||
let postData = {
|
||||
id: this.selectedTagId
|
||||
}
|
||||
this.$api.call(this.endpoint.deleteTag(postData))
|
||||
await this.$api.call(this.endpoint.deleteTag(postData))
|
||||
this.$emit("deleted", this.selectedTagId)
|
||||
this.$refs.tagModal.hide();
|
||||
},
|
||||
@@ -206,7 +210,7 @@ export default {
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="plus_button_container" @mouseleave="hideList">
|
||||
<div v-if="isListItemShown" class="plus_button_container" @mouseleave="hideList">
|
||||
<span :title="values.length === 0 ? 'Bitte Zeilen markieren' : ''">
|
||||
<button @mouseover="showList = true"
|
||||
:disabled="!values || values.length === 0"
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import AppMenu from "../AppMenu.js";
|
||||
|
||||
export default {
|
||||
name: "TempusAppMenu",
|
||||
components: {
|
||||
AppMenu,
|
||||
},
|
||||
template: /* html */ `
|
||||
<aside id="appMenu" class="bg-light offcanvas offcanvas-start col-md p-md-0 h-100">
|
||||
<div class="offcanvas-header">
|
||||
Tempus
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close text-reset"
|
||||
data-bs-dismiss="offcanvas"
|
||||
:aria-label="$p.t('ui/schliessen')"
|
||||
></button>
|
||||
</div>
|
||||
<div class="offcanvas-body">
|
||||
<app-menu app-identifier="tempus" />
|
||||
</div>
|
||||
</aside>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,109 @@
|
||||
import CoreSearchbar from "../searchbar/searchbar.js";
|
||||
import NavLanguage from "../navigation/Language.js";
|
||||
|
||||
export default {
|
||||
name: "TempusHeader",
|
||||
components: {
|
||||
CoreSearchbar,
|
||||
NavLanguage,
|
||||
},
|
||||
props: {
|
||||
tempusRoot: String,
|
||||
avatarUrl: String,
|
||||
logoutUrl: String,
|
||||
searchbaroptions: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
searchfunction: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ["focus-searchbar"],
|
||||
methods: {
|
||||
focusSearchbar() {
|
||||
this.$refs.searchbar?.$refs?.input?.focus();
|
||||
this.$emit("focus-searchbar");
|
||||
},
|
||||
},
|
||||
template: /* html */ `
|
||||
<header class="navbar navbar-expand-lg navbar-dark bg-dark flex-md-nowrap p-0 shadow">
|
||||
<div class="col-md-4 col-lg-3 col-xl-2 d-flex align-items-center">
|
||||
<button
|
||||
class="btn btn-outline-light border-0 m-1 collapsed"
|
||||
type="button"
|
||||
data-bs-toggle="offcanvas"
|
||||
data-bs-target="#appMenu"
|
||||
aria-controls="appMenu"
|
||||
aria-expanded="false"
|
||||
:aria-label="$p.t('ui/toggle_nav')"
|
||||
>
|
||||
<span class="svg-icon svg-icon-apps"></span>
|
||||
</button>
|
||||
<a class="navbar-brand me-0" :href="tempusRoot">Tempus</a>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-outline-light border-0 d-md-none m-1 collapsed"
|
||||
type="button"
|
||||
data-bs-toggle="offcanvas"
|
||||
data-bs-target="#sidebarMenu"
|
||||
aria-controls="sidebarMenu"
|
||||
aria-expanded="false"
|
||||
:aria-label="$p.t('ui/toggle_nav')"
|
||||
>
|
||||
<span class="fa-solid fa-table-list"></span>
|
||||
</button>
|
||||
<core-searchbar
|
||||
ref="searchbar"
|
||||
:searchoptions="searchbaroptions"
|
||||
:searchfunction="searchfunction"
|
||||
class="searchbar position-relative w-100"
|
||||
show-btn-submit
|
||||
></core-searchbar>
|
||||
<div id="nav-user" class="dropdown">
|
||||
<button
|
||||
id="nav-user-btn"
|
||||
class="btn btn-link rounded-0 py-0"
|
||||
type="button"
|
||||
data-bs-toggle="dropdown"
|
||||
data-bs-target="#nav-user-menu"
|
||||
aria-expanded="false"
|
||||
aria-controls="nav-user-menu"
|
||||
>
|
||||
<img
|
||||
:src="avatarUrl"
|
||||
:alt="$p.t('profilUpdate/profilBild')"
|
||||
class="bg-light avatar rounded-circle border border-light"
|
||||
/>
|
||||
</button>
|
||||
<ul
|
||||
ref="navUserDropdown"
|
||||
class="dropdown-menu dropdown-menu-dark dropdown-menu-end rounded-0 text-center m-0"
|
||||
aria-labelledby="nav-user-btn"
|
||||
>
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
class="dropdown-item"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#configModal"
|
||||
>
|
||||
{{ $p.t('ui/settings') }}
|
||||
</button>
|
||||
</li>
|
||||
<li><hr class="dropdown-divider m-0"/></li>
|
||||
<li>
|
||||
<nav-language item-class="dropdown-item border-left-dark" />
|
||||
</li>
|
||||
<li><hr class="dropdown-divider m-0"/></li>
|
||||
<li>
|
||||
<a class="dropdown-item" :href="logoutUrl">
|
||||
{{ $p.t('ui/logout') }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</header>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 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/>.
|
||||
*/
|
||||
import BsModal from "../Bootstrap/Modal.js";
|
||||
|
||||
export default {
|
||||
name: "HistoryModal",
|
||||
components: {
|
||||
BsModal,
|
||||
},
|
||||
props: {
|
||||
entries: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
show() {
|
||||
this.$refs.modal.show();
|
||||
},
|
||||
hide() {
|
||||
this.$refs.modal.hide();
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<bs-modal
|
||||
ref="modal"
|
||||
class="bootstrap-prompt"
|
||||
dialog-class="modal-lg"
|
||||
data-cy="historyModal"
|
||||
>
|
||||
<template #title>History</template>
|
||||
<template #default>
|
||||
<table v-if="entries.length" class="table table-bordered table-hover">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Von</th>
|
||||
<th>Bis</th>
|
||||
<th>Status</th>
|
||||
<th>Ort</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="entry in entries" :key="entry.id">
|
||||
<td>{{ entry.von }}</td>
|
||||
<td>{{ entry.bis }}</td>
|
||||
<td>{{ entry.status_kurzbz }}</td>
|
||||
<td>{{ entry.ort }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
</bs-modal>
|
||||
`,
|
||||
};
|
||||
@@ -77,7 +77,43 @@ export default {
|
||||
return luxon.Duration
|
||||
.fromISOTime(this.event.ende)
|
||||
.toISOTime({ suppressSeconds: true });
|
||||
}
|
||||
},
|
||||
tags() {
|
||||
if (typeof this.event.tags === 'string') {
|
||||
try {
|
||||
return JSON.parse(this.event.tags);
|
||||
} catch (e) {
|
||||
console.error('Failed to parse tags:', e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
return this.event.tags || [];
|
||||
},
|
||||
tagsTooltip() {
|
||||
if (this.tags.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return this.tags.map(tag => tag.beschreibung).join("\n");
|
||||
},
|
||||
resourcesTooltip() {
|
||||
if (!this.event.resources || this.event.resources.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let resources = this.event.resources;
|
||||
if (typeof this.event.resources === 'string') {
|
||||
try {
|
||||
resources = JSON.parse(this.event.resources);
|
||||
} catch (e) {
|
||||
console.error('Failed to parse resources:', e);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
return resources.map(resource => resource.beschreibung).join("\n");
|
||||
},
|
||||
},
|
||||
template: /*html*/`
|
||||
<div
|
||||
@@ -85,9 +121,16 @@ export default {
|
||||
class="position-relative"
|
||||
@wheel.stop
|
||||
>
|
||||
<div v-if="event.has_assigned_resources" class="position-absolute top-0 start-0 m-1" >
|
||||
<div v-if="event.has_assigned_resources || tags?.length" class="position-absolute top-0 start-0 m-1 d-flex gap-1" >
|
||||
<i
|
||||
class="fa-solid fa-table-list text-muted"
|
||||
v-tooltip="resourcesTooltip"
|
||||
v-if="event.has_assigned_resources"
|
||||
class="fa-solid fa-table-list text-muted"
|
||||
></i>
|
||||
<i
|
||||
v-tooltip="tagsTooltip"
|
||||
v-if="tags?.length"
|
||||
class="fa-solid fa-tags"
|
||||
></i>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -53,6 +53,31 @@ export default {
|
||||
ortString() {
|
||||
return Array.isArray(this.event.ort_kurzbz) ? this.event.ort_kurzbz.join(', ') : this.event.ort_kurzbz;
|
||||
},
|
||||
tags() {
|
||||
if (typeof this.event.tags === 'string') {
|
||||
try {
|
||||
return JSON.parse(this.event.tags);
|
||||
} catch (e) {
|
||||
console.error('Failed to parse tags:', e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
return this.event.tags || [];
|
||||
},
|
||||
resourcesValue() {
|
||||
let resources = this.event.resources;
|
||||
if (typeof this.event.resources === 'string') {
|
||||
try {
|
||||
resources = JSON.parse(this.event.resources);
|
||||
} catch (e) {
|
||||
console.error('Failed to parse resources:', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return resources.map(resource => resource.beschreibung).join(', ');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
mehtodNumberPadding: function (number) {
|
||||
@@ -78,7 +103,7 @@ export default {
|
||||
});
|
||||
}
|
||||
},
|
||||
template: `
|
||||
template: /*html*/`
|
||||
<div>
|
||||
<h5>
|
||||
{{$p.t('lvinfo','lehrveranstaltungsinformationen')}}
|
||||
@@ -143,6 +168,33 @@ export default {
|
||||
}}</th>
|
||||
<td>{{event.organisationseinheit}}</td>
|
||||
</tr>
|
||||
<tr v-if="resourcesValue">
|
||||
<th>{{
|
||||
$p.t('ui','betriebsmittel')?
|
||||
$p.t('ui','betriebsmittel')+':'
|
||||
:''
|
||||
}}</th>
|
||||
<td>
|
||||
{{ resourcesValue }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="tags.length">
|
||||
<th>{{
|
||||
$p.t('ui','tags')?
|
||||
$p.t('ui','tags')+':'
|
||||
:''
|
||||
}}</th>
|
||||
<td>
|
||||
<div>
|
||||
<span
|
||||
v-for="tag in tags"
|
||||
:key="tag.tag_typ_kurzbz"
|
||||
:class="[tag.style, { tag_done: tag.done }]"
|
||||
class="tag disabled"
|
||||
>{{ tag.beschreibung }}</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -0,0 +1,282 @@
|
||||
/**
|
||||
* 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/>.
|
||||
*/
|
||||
import BsModal from "../Bootstrap/Modal.js";
|
||||
import FormInput from "../Form/Input.js";
|
||||
import ApiOperationalResourceToCalender from "../../api/factory/operationalResourceToCalender.js";
|
||||
|
||||
export default {
|
||||
name: "ResourcesAssignmentModal",
|
||||
components: {
|
||||
BsModal,
|
||||
FormInput,
|
||||
},
|
||||
emits: ["saveFinished"],
|
||||
data() {
|
||||
return {
|
||||
calendar: null,
|
||||
availableResources: [],
|
||||
filteredAvailableResources: [],
|
||||
selectedAvailableResource: null,
|
||||
assignedResources: [],
|
||||
areFormButtonsDisplayed: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
dropdownParsedAvailableResources() {
|
||||
return this.availableResources
|
||||
.map((unit) => ({
|
||||
label: unit.beschreibung,
|
||||
value: unit.betriebsmittel_id,
|
||||
data: unit,
|
||||
}))
|
||||
.sort((a, b) => a.label?.localeCompare(b.label));
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
selectedAvailableResource(newValue) {
|
||||
if (!newValue) return;
|
||||
|
||||
this.assignedResources.push({
|
||||
betriebsmittel_kalender_id: null,
|
||||
betriebsmittel_id: newValue.data.betriebsmittel_id,
|
||||
beschreibung: newValue.data.beschreibung,
|
||||
anmerkung: "",
|
||||
isNoteTextareaShown: false,
|
||||
});
|
||||
|
||||
this.areFormButtonsDisplayed = true;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async open(calendar) {
|
||||
if (!calendar?.kalender_id) return;
|
||||
|
||||
this.calendar = calendar;
|
||||
this.availableResources =
|
||||
await this.fetchSchedulableResourcesByCalender(calendar.kalender_id);
|
||||
this.filteredAvailableResources = [
|
||||
...this.dropdownParsedAvailableResources,
|
||||
];
|
||||
this.assignedResources =
|
||||
await this.fetchAssignedResourcesByCalender(calendar.kalender_id);
|
||||
|
||||
this.show();
|
||||
},
|
||||
show() {
|
||||
this.$refs.modal.show();
|
||||
},
|
||||
hide() {
|
||||
this.$refs.modal.hide();
|
||||
},
|
||||
reset() {
|
||||
this.calendar = null;
|
||||
this.availableResources = [];
|
||||
this.filteredAvailableResources = [];
|
||||
this.selectedAvailableResource = null;
|
||||
this.assignedResources = [];
|
||||
this.areFormButtonsDisplayed = false;
|
||||
},
|
||||
async fetchAssignedResourcesByCalender(calenderId) {
|
||||
const result = await this.$api.call(
|
||||
ApiOperationalResourceToCalender.getAssignedResourcesByCalender(
|
||||
calenderId,
|
||||
),
|
||||
);
|
||||
|
||||
if (result.meta.status === "success") {
|
||||
return result.data
|
||||
.filter((unit) => !!unit)
|
||||
.map((unit) => ({
|
||||
isNoteTextareaShown:
|
||||
unit.anmerkung && unit.anmerkung.trim() !== "",
|
||||
...unit,
|
||||
}));
|
||||
}
|
||||
|
||||
this.$fhcAlert.alertError(
|
||||
this.$p.t("ui", "failed_assigned_resources_fetch_error_message"),
|
||||
);
|
||||
return [];
|
||||
},
|
||||
async fetchSchedulableResourcesByCalender(calendarId) {
|
||||
const result = await this.$api.call(
|
||||
ApiOperationalResourceToCalender.getSchedulableResourcesByCalendar(
|
||||
calendarId,
|
||||
),
|
||||
);
|
||||
|
||||
if (result.meta.status === "success") return result.data;
|
||||
|
||||
this.$fhcAlert.alertError(
|
||||
this.$p.t("ui", "failed_schedulable_resources_fetch_error_message"),
|
||||
);
|
||||
return [];
|
||||
},
|
||||
filterAvailableResources(event) {
|
||||
const query = event.query.toLowerCase();
|
||||
const unassignedResources = this.dropdownParsedAvailableResources.filter(
|
||||
(unit) =>
|
||||
!this.assignedResources.some(
|
||||
(assigned) => assigned.betriebsmittel_id === unit.value,
|
||||
),
|
||||
);
|
||||
|
||||
this.filteredAvailableResources = query
|
||||
? unassignedResources.filter((unit) =>
|
||||
unit.label.toLowerCase().includes(query),
|
||||
)
|
||||
: unassignedResources;
|
||||
},
|
||||
toggleAssignedResourceNoteInput(resource) {
|
||||
const assignedResource = this.assignedResources.find(
|
||||
(assigned) => assigned.betriebsmittel_id === resource.betriebsmittel_id,
|
||||
);
|
||||
|
||||
if (assignedResource) {
|
||||
assignedResource.isNoteTextareaShown =
|
||||
!assignedResource.isNoteTextareaShown;
|
||||
}
|
||||
|
||||
this.areFormButtonsDisplayed = true;
|
||||
},
|
||||
removeAssignedResource(resource) {
|
||||
this.assignedResources = this.assignedResources.filter(
|
||||
(assigned) =>
|
||||
assigned.betriebsmittel_id !== resource.betriebsmittel_id,
|
||||
);
|
||||
this.areFormButtonsDisplayed = true;
|
||||
},
|
||||
async refreshResourcesAssignmentModalData() {
|
||||
if (!this.calendar?.kalender_id) return;
|
||||
|
||||
this.availableResources =
|
||||
await this.fetchSchedulableResourcesByCalender(
|
||||
this.calendar.kalender_id,
|
||||
);
|
||||
this.filteredAvailableResources = [
|
||||
...this.dropdownParsedAvailableResources,
|
||||
];
|
||||
this.assignedResources = await this.fetchAssignedResourcesByCalender(
|
||||
this.calendar.kalender_id,
|
||||
);
|
||||
this.selectedAvailableResource = null;
|
||||
this.areFormButtonsDisplayed = false;
|
||||
},
|
||||
async saveAssignedResourcesToCalendarItem() {
|
||||
if (!this.calendar?.kalender_id) return;
|
||||
|
||||
const result = await this.$api.call(
|
||||
ApiOperationalResourceToCalender.storeResourcesToCalendarRelationship(
|
||||
this.calendar.kalender_id,
|
||||
this.assignedResources,
|
||||
),
|
||||
);
|
||||
|
||||
if (result.meta.status === "success") {
|
||||
this.$fhcAlert.alertSuccess(
|
||||
this.$p.t("ui", "assigned_resources_save_success_message"),
|
||||
);
|
||||
await this.refreshResourcesAssignmentModalData();
|
||||
} else {
|
||||
this.$fhcAlert.alertError(
|
||||
this.$p.t("ui", "failed_assigned_resources_save_error_message"),
|
||||
);
|
||||
}
|
||||
|
||||
this.$emit("saveFinished");
|
||||
this.hide();
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<bs-modal
|
||||
ref="modal"
|
||||
@hide-bs-modal="reset"
|
||||
class="bootstrap-prompt"
|
||||
data-cy="resourcesAssignmentModal"
|
||||
>
|
||||
<template #title>{{ $p.t('ui', 'resource_assignment_modal_title') }}</template>
|
||||
<template #default>
|
||||
<div class="mb-5">
|
||||
<form-input
|
||||
v-if="availableResources.length"
|
||||
@item-select="selectedAvailableResource = $event.value"
|
||||
:label="$p.t('ui', 'available_resources_label')"
|
||||
:suggestions="filteredAvailableResources"
|
||||
:option-value="option => option.value"
|
||||
:option-label="option => option.label"
|
||||
@complete="filterAvailableResources"
|
||||
dropdown
|
||||
force-selection
|
||||
type="autocomplete"
|
||||
name="availableResources"
|
||||
:close-on-select="false"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div class="d-flex align-items-center justify-content-between mb-2">
|
||||
<h6 class="mb-2 mx-auto text-bold fw-1">
|
||||
{{ $p.t('ui', 'assigned_resources_subtitle') }}
|
||||
</h6>
|
||||
</div>
|
||||
<div v-if="assignedResources.length" class="mb-4">
|
||||
<div
|
||||
v-for="resource in assignedResources"
|
||||
:key="resource.betriebsmittel_id"
|
||||
class="shadow-sm p-2 mb-2 bg-body rounded"
|
||||
>
|
||||
<div class="d-flex justify-content-between align-items-center mb-1">
|
||||
<p class="m-0">{{ resource.beschreibung }}</p>
|
||||
<div class="d-flex justify-content-between align-items-center gap-2">
|
||||
<a href="#" @click.prevent="toggleAssignedResourceNoteInput(resource)" class="ms-auto">
|
||||
<i class="fa fa-edit text-primary"></i>
|
||||
</a>
|
||||
<a href="#" @click.prevent="removeAssignedResource(resource)" class="ms-auto">
|
||||
<i class="fa fa-trash text-danger"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<form-input
|
||||
v-if="resource.isNoteTextareaShown"
|
||||
v-model="resource.anmerkung"
|
||||
@input="areFormButtonsDisplayed = true"
|
||||
:placeholder="$capitalize($p.t('global/anmerkung'))"
|
||||
:rows="1"
|
||||
class="flex-grow-1"
|
||||
type="textarea"
|
||||
name="anmerkung"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="d-flex align-items-center justify-content-center mb-2">
|
||||
<p class="text-muted mb-0">{{ $p.t('ui', 'no_assigned_resources') }}</p>
|
||||
</div>
|
||||
<div
|
||||
v-if="areFormButtonsDisplayed"
|
||||
class="d-flex justify-content-end gap-2"
|
||||
>
|
||||
<button type="button" class="btn btn-secondary" @click="refreshResourcesAssignmentModalData">
|
||||
{{ $p.t('ui', 'abbrechen') }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" @click="saveAssignedResourcesToCalendarItem">
|
||||
{{ $p.t('ui', 'speichern') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</bs-modal>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,168 @@
|
||||
import FhcCoursepicker from "./Coursepicker.js";
|
||||
import LectureSelection from "./Filters/LectureSelection.js";
|
||||
import VerbandSelection from "./Filters/VerbandSelection.js";
|
||||
import RoomSelection from "./Filters/RoomSelection.js";
|
||||
import ParkingSlot from "./ParkingSlot.js";
|
||||
import StvStudiensemester from "../Stv/Studentenverwaltung/Studiensemester.js";
|
||||
|
||||
export default {
|
||||
name: "TempusSidebarMenu",
|
||||
components: {
|
||||
FhcCoursepicker,
|
||||
LectureSelection,
|
||||
VerbandSelection,
|
||||
RoomSelection,
|
||||
ParkingSlot,
|
||||
StvStudiensemester,
|
||||
},
|
||||
props: {
|
||||
previewRole: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
rooms: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
studiengaenge: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
studiengaengeAll: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
lecturers: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
selectedStudiensemester: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: [
|
||||
"update:previewRole",
|
||||
"update:rooms",
|
||||
"update:studiengaenge",
|
||||
"update:parkedKeys",
|
||||
"update:selectedStudiensemester",
|
||||
"remove-lecturer",
|
||||
"select-lecturer",
|
||||
"select-kw",
|
||||
"sync",
|
||||
],
|
||||
methods: {
|
||||
reloadCoursepicker() {
|
||||
return this.$refs.coursepicker?.reload();
|
||||
},
|
||||
isParked(id) {
|
||||
return this.$refs.parking?.isParked(id) ?? false;
|
||||
},
|
||||
park(items) {
|
||||
return this.$refs.parking?.park(null, items);
|
||||
},
|
||||
unpark(item) {
|
||||
return this.$refs.parking?.unpark(item);
|
||||
},
|
||||
},
|
||||
template: /* html */ `
|
||||
<nav
|
||||
id="sidebarMenu"
|
||||
class="bg-light offcanvas offcanvas-start col-md p-md-0 h-100 d-flex flex-column"
|
||||
>
|
||||
<div class="sidebar-icons d-flex flex-row align-items-start py-2 gap-1 ps-2">
|
||||
<button
|
||||
class="btn btn-outline-secondary"
|
||||
type="button"
|
||||
data-bs-toggle="offcanvas"
|
||||
data-bs-target="#verbandMenu"
|
||||
aria-controls="verbandMenu"
|
||||
aria-expanded="false"
|
||||
title="Verband"
|
||||
>
|
||||
<span class="fa-solid fa-university"></span>
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-outline-secondary"
|
||||
type="button"
|
||||
data-bs-toggle="offcanvas"
|
||||
data-bs-target="#verbandMenu"
|
||||
aria-controls="verbandMenu"
|
||||
aria-expanded="false"
|
||||
title="Verband"
|
||||
>
|
||||
<span class="fa-solid fa-door-open"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="px-2 py-1 w-100">
|
||||
<div
|
||||
class="d-flex gap-1 py-1"
|
||||
data-cy="previewRoleOptionsHolder"
|
||||
>
|
||||
<button
|
||||
class="btn btn-sm"
|
||||
:class="previewRole === 'planer' ? 'btn-dark' : 'btn-outline-dark'"
|
||||
@click="$emit('update:previewRole', 'planer')"
|
||||
>
|
||||
<i class="fa-solid fa-pen-ruler me-1"></i>Planer
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm"
|
||||
:class="previewRole === 'lektor' ? 'btn-primary' : 'btn-outline-primary'"
|
||||
@click="$emit('update:previewRole', 'lektor')"
|
||||
>
|
||||
<i class="fa-solid fa-chalkboard-user me-1"></i>Lektor
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm"
|
||||
:class="previewRole === 'student' ? 'btn-success' : 'btn-outline-success'"
|
||||
@click="$emit('update:previewRole', 'student')"
|
||||
>
|
||||
<i class="fa-solid fa-user-graduate me-1"></i>Student
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm btn-outline-danger"
|
||||
@click="$emit('sync')"
|
||||
>
|
||||
<i class="fa-solid fa-rotate me-1"></i>Sync
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<room-selection
|
||||
v-if="rooms.length"
|
||||
:rooms="rooms"
|
||||
@update:rooms="$emit('update:rooms', $event)"
|
||||
></room-selection>
|
||||
<verband-selection
|
||||
v-if="studiengaenge.length"
|
||||
:studiengaenge="studiengaenge"
|
||||
:studiengaenge-all="studiengaengeAll"
|
||||
@update:studiengaenge="$emit('update:studiengaenge', $event)"
|
||||
></verband-selection>
|
||||
<lecture-selection
|
||||
v-if="lecturers.length"
|
||||
:lecturers="lecturers"
|
||||
@remove="$emit('remove-lecturer', $event)"
|
||||
></lecture-selection>
|
||||
<div class="d-flex flex-column flex-grow-1" style="min-height: 0">
|
||||
<parking-slot
|
||||
ref="parking"
|
||||
@update:parked-keys="$emit('update:parkedKeys', $event)"
|
||||
></parking-slot>
|
||||
|
||||
<fhc-coursepicker
|
||||
ref="coursepicker"
|
||||
:studiengaenge="studiengaenge"
|
||||
:studiensemester="selectedStudiensemester"
|
||||
@select-lecturer="$emit('select-lecturer', $event)"
|
||||
@select-kw="$emit('select-kw', $event)"
|
||||
></fhc-coursepicker>
|
||||
</div>
|
||||
<stv-studiensemester
|
||||
:studiensemester-kurzbz="selectedStudiensemester"
|
||||
@update:studiensemester-kurzbz="$emit('update:selectedStudiensemester', $event)"
|
||||
></stv-studiensemester>
|
||||
</nav>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,239 @@
|
||||
/**
|
||||
* 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/>.
|
||||
*/
|
||||
import BsModal from "../Bootstrap/Modal.js";
|
||||
import FormInput from "../Form/Input.js";
|
||||
import CoreTag from "../Tag/Tag.js";
|
||||
import ApiTempusTag from "../../api/factory/tempus/tag.js";
|
||||
|
||||
export default {
|
||||
name: "TagsAssignmentModal",
|
||||
components: {
|
||||
BsModal,
|
||||
FormInput,
|
||||
CoreTag,
|
||||
},
|
||||
emits: ["tagsChanged"],
|
||||
data() {
|
||||
return {
|
||||
calendar: null,
|
||||
availableTags: [],
|
||||
filteredAvailableTags: [],
|
||||
selectedAvailableTag: null,
|
||||
assignedTags: [],
|
||||
tagEndpoint: ApiTempusTag,
|
||||
tagModalEscapeHandler: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
dropdownParsedAvailableTags() {
|
||||
return this.availableTags.map((tag) => ({
|
||||
label: tag.bezeichnung,
|
||||
value: tag.tag_typ_kurzbz,
|
||||
data: tag,
|
||||
}));
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async open(calendar) {
|
||||
if (!calendar?.kalender_id) return;
|
||||
|
||||
this.calendar = calendar;
|
||||
this.availableTags = await this.fetchAvailableTags();
|
||||
this.filteredAvailableTags = [...this.dropdownParsedAvailableTags];
|
||||
this.assignedTags = await this.fetchAssignedTagsByCalender(
|
||||
calendar.eindeutige_gruppen_id,
|
||||
);
|
||||
|
||||
this.show();
|
||||
},
|
||||
show() {
|
||||
this.$refs.modal.show();
|
||||
},
|
||||
hide() {
|
||||
this.$refs.modal.hide();
|
||||
},
|
||||
reset() {
|
||||
this.removeTagModalEscapeHandler();
|
||||
this.calendar = null;
|
||||
this.availableTags = [];
|
||||
this.filteredAvailableTags = [];
|
||||
this.selectedAvailableTag = null;
|
||||
this.assignedTags = [];
|
||||
},
|
||||
prepareTagModal() {
|
||||
const tagModal = this.$refs.tagComponent?.$refs.tagModal;
|
||||
const tagModalElement = tagModal?.$el;
|
||||
|
||||
if (!tagModalElement) return;
|
||||
|
||||
tagModalElement.addEventListener(
|
||||
"shown.bs.modal",
|
||||
() => {
|
||||
this.tagModalEscapeHandler = (event) => {
|
||||
if (event.key !== "Escape") return;
|
||||
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
tagModal.hide();
|
||||
};
|
||||
document.addEventListener("keydown", this.tagModalEscapeHandler, true);
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
tagModalElement.addEventListener(
|
||||
"hidden.bs.modal",
|
||||
() => this.restoreAssignmentModalFocus(),
|
||||
{ once: true },
|
||||
);
|
||||
},
|
||||
removeTagModalEscapeHandler() {
|
||||
if (!this.tagModalEscapeHandler) return;
|
||||
|
||||
document.removeEventListener(
|
||||
"keydown",
|
||||
this.tagModalEscapeHandler,
|
||||
true,
|
||||
);
|
||||
this.tagModalEscapeHandler = null;
|
||||
},
|
||||
restoreAssignmentModalFocus() {
|
||||
this.removeTagModalEscapeHandler();
|
||||
this.$refs.modal?.$el?.focus();
|
||||
},
|
||||
async fetchAvailableTags() {
|
||||
const result = await this.$api.call(ApiTempusTag.getTags());
|
||||
|
||||
if (result.meta.status === "success") return result.data;
|
||||
|
||||
this.$fhcAlert.alertError(
|
||||
this.$p.t("ui", "failed_available_tags_fetch_error_message"),
|
||||
);
|
||||
return [];
|
||||
},
|
||||
async fetchAssignedTagsByCalender(calendarGroupId) {
|
||||
const result = await this.$api.call(
|
||||
ApiTempusTag.getTagsByCalendar(calendarGroupId),
|
||||
);
|
||||
|
||||
if (result.meta.status === "success") {
|
||||
return result.data.filter((tag) => !!tag);
|
||||
}
|
||||
|
||||
this.$fhcAlert.alertError(
|
||||
this.$p.t("ui", "failed_assigned_tags_fetch_error_message"),
|
||||
);
|
||||
return [];
|
||||
},
|
||||
filterAvailableTags(event) {
|
||||
const query = event.query.toLowerCase();
|
||||
const unassignedTags = this.dropdownParsedAvailableTags.filter(
|
||||
(tag) =>
|
||||
!this.assignedTags.some(
|
||||
(assigned) => assigned.tag_id === tag.value,
|
||||
),
|
||||
);
|
||||
|
||||
this.filteredAvailableTags = query
|
||||
? unassignedTags.filter((tag) =>
|
||||
tag.label.toLowerCase().includes(query),
|
||||
)
|
||||
: unassignedTags;
|
||||
},
|
||||
selectTag(event) {
|
||||
this.selectedAvailableTag = event.value;
|
||||
this.prepareTagModal();
|
||||
this.$refs.tagComponent?.openModal(event.value.data);
|
||||
},
|
||||
editTag(tag) {
|
||||
this.prepareTagModal();
|
||||
this.$refs.tagComponent?.editTag(tag.notiz_id);
|
||||
},
|
||||
async handleCalendarTagChange() {
|
||||
this.removeTagModalEscapeHandler();
|
||||
|
||||
const calendarGroupId = this.calendar?.eindeutige_gruppen_id;
|
||||
if (!calendarGroupId) return;
|
||||
|
||||
this.assignedTags =
|
||||
await this.fetchAssignedTagsByCalender(calendarGroupId);
|
||||
this.$emit("tagsChanged");
|
||||
},
|
||||
},
|
||||
template: /* html */`
|
||||
<bs-modal
|
||||
ref="modal"
|
||||
@hide-bs-modal="reset"
|
||||
body-class="p-4"
|
||||
class="bootstrap-prompt"
|
||||
data-cy="tagsAssignmentModal"
|
||||
>
|
||||
<template #title>{{ $p.t('ui', 'tags_assignment_modal_title') }}</template>
|
||||
<template #default>
|
||||
<div class="mb-5">
|
||||
<form-input
|
||||
v-if="availableTags.length"
|
||||
@item-select="selectTag"
|
||||
:label="$capitalize($p.t('ui', 'tags'))"
|
||||
:suggestions="filteredAvailableTags"
|
||||
:option-value="option => option.value"
|
||||
:option-label="option => option.label"
|
||||
@complete="filterAvailableTags"
|
||||
dropdown
|
||||
force-selection
|
||||
type="autocomplete"
|
||||
name="availableTags"
|
||||
:close-on-select="false"
|
||||
>
|
||||
<template #option="{ option }">
|
||||
<span :class="['tag', option.data.style]">{{ option.label }}</span>
|
||||
</template>
|
||||
</form-input>
|
||||
</div>
|
||||
<div>
|
||||
<div class="d-flex align-items-center justify-content-between mb-2">
|
||||
<h6 class="mb-1 mx-auto text-bold fw-1">
|
||||
{{ $p.t('ui', 'assigned_tags_subtitle') }}
|
||||
</h6>
|
||||
</div>
|
||||
<div v-if="assignedTags.length">
|
||||
<span
|
||||
v-for="tag in assignedTags"
|
||||
:key="tag.tag_typ_kurzbz"
|
||||
:class="[tag.style, { tag_done: tag.done }]"
|
||||
@click="editTag(tag)"
|
||||
class="tag"
|
||||
>{{ tag.bezeichnung }}</span>
|
||||
</div>
|
||||
<div v-else class="d-flex align-items-center justify-content-center mb-2">
|
||||
<p class="text-muted mb-0">{{ $p.t('ui', 'no_assigned_tags') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</bs-modal>
|
||||
<core-tag
|
||||
v-if="calendar?.eindeutige_gruppen_id"
|
||||
ref="tagComponent"
|
||||
:is-list-item-shown="false"
|
||||
:endpoint="tagEndpoint"
|
||||
:values="[calendar.eindeutige_gruppen_id]"
|
||||
zuordnung_typ="eindeutige_kalender_gruppen_id"
|
||||
@added="handleCalendarTagChange"
|
||||
@deleted="handleCalendarTagChange"
|
||||
@updated="handleCalendarTagChange"
|
||||
/>
|
||||
`,
|
||||
};
|
||||
@@ -14,60 +14,51 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import CoreSearchbar from "../searchbar/searchbar.js";
|
||||
import NavLanguage from "../navigation/Language.js";
|
||||
import VerticalSplit from "../verticalsplit/verticalsplit.js";
|
||||
import FhcCalendar from "../Calendar/Tempus.js";
|
||||
import FhcCoursepicker from "./Coursepicker.js";
|
||||
import LectureSelection from "./Filters/LectureSelection.js";
|
||||
import VerbandSelection from "./Filters/VerbandSelection.js";
|
||||
import RoomSelection from "./Filters/RoomSelection.js";
|
||||
import ParkingSlot from "./ParkingSlot.js";
|
||||
import ApiKalender from "../../api/factory/tempus/kalender.js";
|
||||
import ApiSearchbar from "../../api/factory/searchbar.js";
|
||||
import ApiRenderers from "../../api/factory/renderers.js";
|
||||
import ApiTempusConfig from "../../api/factory/tempus/config.js";
|
||||
import ApiBetriebsmittel from "../../api/factory/betriebsmittel.js";
|
||||
import ApiOperationalResourceToCalender from "../../api/factory/operationalResourceToCalender.js";
|
||||
import AppMenu from "../AppMenu.js";
|
||||
import drop from "../../directives/drop.js";
|
||||
import AppConfig from "../AppConfig.js";
|
||||
import BsModal from "../Bootstrap/Modal.js";
|
||||
|
||||
import StvVerband from "../Stv/Studentenverwaltung/Verband.js";
|
||||
import ApiStudiengangTree from "../../api/factory/tempus/studiengangtree.js";
|
||||
import ApiInfo from "../../api/factory/tempus/info.js";
|
||||
import StvStudiensemester from "../Stv/Studentenverwaltung/Studiensemester.js";
|
||||
import FormInput from "../../../js/components/Form/Input.js";
|
||||
import Reservierung from "./Reservierung.js";
|
||||
import { getTempusShortcuts } from "./shortcuts.js";
|
||||
import KeyboardShortcuts from "./KeyboardShortcuts.js";
|
||||
import { useContextMenuActions } from "../../composables/Tempus/ContextMenuActions.js";
|
||||
import MultiWeekPlanModal from "./MultiWeekPlanModal.js";
|
||||
import HistoryModal from "./HistoryModal.js";
|
||||
import ResourcesAssignmentModal from "./ResourcesAssignmentModal.js";
|
||||
import TagsAssignmentModal from "./TagsAssignmentModal.js";
|
||||
import { getTempusSearchbarOptions } from "./Filters/searchbarOptions.js";
|
||||
import TempusHeader from "./Header.js";
|
||||
import TempusAppMenu from "./AppMenu.js";
|
||||
import TempusVerbandMenu from "./VerbandMenu.js";
|
||||
import TempusSidebarMenu from "./SidebarMenu.js";
|
||||
|
||||
export default {
|
||||
name: "Tempus",
|
||||
components: {
|
||||
CoreSearchbar,
|
||||
VerticalSplit,
|
||||
FhcCalendar,
|
||||
FhcCoursepicker,
|
||||
LectureSelection,
|
||||
VerbandSelection,
|
||||
RoomSelection,
|
||||
ParkingSlot,
|
||||
AppConfig,
|
||||
AppMenu,
|
||||
NavLanguage,
|
||||
TempusAppMenu,
|
||||
TempusHeader,
|
||||
BsModal,
|
||||
StvVerband,
|
||||
StvStudiensemester,
|
||||
TempusVerbandMenu,
|
||||
TempusSidebarMenu,
|
||||
Multiselect: primevue.multiselect,
|
||||
FormInput,
|
||||
Reservierung,
|
||||
KeyboardShortcuts,
|
||||
MultiWeekPlanModal,
|
||||
HistoryModal,
|
||||
ResourcesAssignmentModal,
|
||||
TagsAssignmentModal,
|
||||
},
|
||||
props: {
|
||||
defaultSemester: String,
|
||||
@@ -92,8 +83,9 @@ export default {
|
||||
appConfig: Vue.computed(() => this.appconfig),
|
||||
contextMenuActions: useContextMenuActions({
|
||||
openRaumauswahl: (orig) => this.openRaumauswahl(orig),
|
||||
openResourcesAssignmentModal: (orig) =>
|
||||
this.openResourcesAssignmentModal(orig),
|
||||
openResourcesAssignmentModal: (orig) =>
|
||||
this.$refs.resourcesAssignmentModal?.open(orig),
|
||||
openTagsModal: (orig) => this.$refs.tagsAssignmentModal?.open(orig),
|
||||
openHistory: (orig) => this.openHistory(orig),
|
||||
deleteEntry: (orig) => this.deleteEntry(orig),
|
||||
syncToLecturer: (orig) => this.syncToLecturer(orig),
|
||||
@@ -161,14 +153,6 @@ export default {
|
||||
endTime: null,
|
||||
},
|
||||
studiengaenge_all: [],
|
||||
resourcesAssignmentModal: {
|
||||
calendar: null,
|
||||
availableResources: [],
|
||||
filteredAvailableResources: [],
|
||||
selectedAvailableResource: null,
|
||||
assignedResources: [],
|
||||
areFormButtonsDisplayed: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -203,17 +187,6 @@ export default {
|
||||
searchbaroptions() {
|
||||
return getTempusSearchbarOptions(this);
|
||||
},
|
||||
dropdownParsedAvailableResources() {
|
||||
return this.resourcesAssignmentModal.availableResources
|
||||
.map((unit) => {
|
||||
return {
|
||||
label: unit.beschreibung,
|
||||
value: unit.betriebsmittel_id,
|
||||
data: unit,
|
||||
};
|
||||
})
|
||||
.sort((a, b) => a.label?.localeCompare(b.label));
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async openRaumauswahl(orig) {
|
||||
@@ -227,27 +200,12 @@ export default {
|
||||
this.$refs.raumModal.show();
|
||||
});
|
||||
},
|
||||
async openResourcesAssignmentModal(orig) {
|
||||
if (!orig?.kalender_id) return;
|
||||
|
||||
this.resourcesAssignmentModal.calendar = orig;
|
||||
this.resourcesAssignmentModal.availableResources =
|
||||
await this.fetchSchedulableResourcesByCalender(orig.kalender_id);
|
||||
this.resourcesAssignmentModal.filteredAvailableResources = [
|
||||
...this.dropdownParsedAvailableResources,
|
||||
];
|
||||
|
||||
this.resourcesAssignmentModal.assignedResources =
|
||||
await this.fetchAssignedResourcesByCalender(orig.kalender_id);
|
||||
|
||||
this.$refs.resourcesAssignmentModal.show();
|
||||
},
|
||||
async deleteEntry(orig) {
|
||||
if (!orig?.kalender_id) return;
|
||||
|
||||
await this.deleteEntryCall(orig);
|
||||
this.$refs.calendar.resetEventLoader();
|
||||
this.$refs.coursepicker.reload();
|
||||
this.$refs.sidebar.reloadCoursepicker();
|
||||
},
|
||||
async deleteEntries(origList) {
|
||||
let validList = (origList ?? []).filter((orig) => orig?.kalender_id);
|
||||
@@ -257,13 +215,16 @@ export default {
|
||||
validList.map((orig) => this.deleteEntryCall(orig)),
|
||||
);
|
||||
this.$refs.calendar.resetEventLoader();
|
||||
this.$refs.coursepicker.reload();
|
||||
this.$refs.sidebar.reloadCoursepicker();
|
||||
},
|
||||
async deleteEntryCall(orig) {
|
||||
await this.$api
|
||||
.call(ApiKalender.deleteEntry(orig.kalender_id))
|
||||
.then(() => {
|
||||
this.$refs.parking.unpark({ type: orig.type, id: orig.kalender_id });
|
||||
this.$refs.sidebar.unpark({
|
||||
type: orig.type,
|
||||
id: orig.kalender_id,
|
||||
});
|
||||
});
|
||||
},
|
||||
async openHistory(orig) {
|
||||
@@ -272,7 +233,7 @@ export default {
|
||||
.call(ApiKalender.getHistory(orig.kalender_id))
|
||||
.then((result) => {
|
||||
this.historyEntries = result.data ?? [];
|
||||
this.$refs.historyModel.show();
|
||||
this.$refs.historyModal.show();
|
||||
});
|
||||
},
|
||||
syncToLecturer(orig) {
|
||||
@@ -304,12 +265,6 @@ export default {
|
||||
this.ort_kurzbz = data.ort_kurzbz;
|
||||
this.rooms = [{ ort_kurzbz: data.ort_kurzbz }];
|
||||
},
|
||||
onSelectVerbandAndClose(payload) {
|
||||
console.log(payload);
|
||||
this.onSelectVerband(payload);
|
||||
bootstrap.Offcanvas.getOrCreateInstance(this.$refs.verbandMenu).hide();
|
||||
},
|
||||
|
||||
onSelectVerband({ link, studiengang_kz, semester, orgform_kurzbz, name }) {
|
||||
if (orgform_kurzbz) {
|
||||
semester = null;
|
||||
@@ -570,7 +525,7 @@ export default {
|
||||
dates.end_time,
|
||||
() => {
|
||||
this.$refs.calendar.resetEventLoader();
|
||||
this.$refs.coursepicker.reload();
|
||||
this.$refs.sidebar.reloadCoursepicker();
|
||||
},
|
||||
);
|
||||
},
|
||||
@@ -639,7 +594,7 @@ export default {
|
||||
.then((result) => result.data)
|
||||
.then((result) => {
|
||||
this.$refs.calendar.resetEventLoader();
|
||||
this.$refs.coursepicker.reload();
|
||||
this.$refs.sidebar.reloadCoursepicker();
|
||||
this.bcc.postMessage("dropped");
|
||||
});
|
||||
} else if (obj.type === "kalender") {
|
||||
@@ -650,7 +605,7 @@ export default {
|
||||
start_time,
|
||||
end_time,
|
||||
() => {
|
||||
this.$refs.parking.unpark({
|
||||
this.$refs.sidebar.unpark({
|
||||
type: obj.type,
|
||||
id: obj.orig.kalender_id,
|
||||
});
|
||||
@@ -674,7 +629,7 @@ export default {
|
||||
},
|
||||
onMultiWeekConfirmed() {
|
||||
this.$refs.calendar.resetEventLoader();
|
||||
this.$refs.coursepicker.reload();
|
||||
this.$refs.sidebar.reloadCoursepicker();
|
||||
this.bcc.postMessage("dropped");
|
||||
},
|
||||
handleRange(range) {
|
||||
@@ -787,138 +742,6 @@ export default {
|
||||
this.extraBackgrounds = res;
|
||||
},
|
||||
|
||||
async fetchAssignedResourcesByCalender(calenderId) {
|
||||
let getAssignedResources = await this.$api.call(
|
||||
ApiOperationalResourceToCalender.getAssignedResourcesByCalender(
|
||||
calenderId,
|
||||
),
|
||||
);
|
||||
if (getAssignedResources.meta.status === "success") {
|
||||
return getAssignedResources.data
|
||||
.filter((unit) => !!unit)
|
||||
.map((unit) => {
|
||||
return {
|
||||
isNoteTextareaShown:
|
||||
unit.anmerkung && unit.anmerkung.trim() !== "",
|
||||
...unit,
|
||||
};
|
||||
})
|
||||
.filter((unit) => !!unit);
|
||||
} else {
|
||||
this.$fhcAlert.alertError(
|
||||
this.$p.t("ui", "failed_assigned_resources_fetch_error_message"),
|
||||
);
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
async fetchSchedulableResourcesByCalender(calendarID) {
|
||||
let getSchedulableResourcesByCalendar = await this.$api.call(
|
||||
ApiOperationalResourceToCalender.getSchedulableResourcesByCalendar(
|
||||
calendarID,
|
||||
),
|
||||
);
|
||||
if (getSchedulableResourcesByCalendar.meta.status === "success") {
|
||||
return getSchedulableResourcesByCalendar.data;
|
||||
} else {
|
||||
this.$fhcAlert.alertError(
|
||||
this.$p.t("ui", "failed_schedulable_resources_fetch_error_message"),
|
||||
);
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
filterAvailableResources(event) {
|
||||
this.resourcesAssignmentModal.filteredAvailableResources;
|
||||
const query = event.query.toLowerCase();
|
||||
if (!query) {
|
||||
return (this.resourcesAssignmentModal.filteredAvailableResources = [
|
||||
...this.dropdownParsedAvailableResources.filter((unit) => {
|
||||
return !this.resourcesAssignmentModal.assignedResources.some(
|
||||
(assigned) => assigned.betriebsmittel_id === unit.value,
|
||||
);
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
return (this.resourcesAssignmentModal.filteredAvailableResources =
|
||||
this.dropdownParsedAvailableResources
|
||||
.filter((unit) => {
|
||||
return !this.resourcesAssignmentModal.assignedResources.some(
|
||||
(assigned) => assigned.betriebsmittel_id === unit.value,
|
||||
);
|
||||
})
|
||||
.filter((unit) => {
|
||||
return unit.label.toLowerCase().includes(query);
|
||||
}));
|
||||
},
|
||||
toggleAssignedResourceNoteInput(resource) {
|
||||
const index = this.resourcesAssignmentModal.assignedResources.findIndex(
|
||||
(assigned) => assigned.betriebsmittel_id === resource.betriebsmittel_id,
|
||||
);
|
||||
if (index !== -1) {
|
||||
this.resourcesAssignmentModal.assignedResources[
|
||||
index
|
||||
].isNoteTextareaShown =
|
||||
!this.resourcesAssignmentModal.assignedResources[index]
|
||||
.isNoteTextareaShown;
|
||||
}
|
||||
|
||||
this.resourcesAssignmentModal.areFormButtonsDisplayed = true;
|
||||
},
|
||||
removeAssignedResource(resource) {
|
||||
this.resourcesAssignmentModal.assignedResources =
|
||||
this.resourcesAssignmentModal.assignedResources.filter(
|
||||
(assigned) =>
|
||||
assigned.betriebsmittel_id !== resource.betriebsmittel_id,
|
||||
);
|
||||
|
||||
this.resourcesAssignmentModal.areFormButtonsDisplayed = true;
|
||||
},
|
||||
async refreshResourcesAssignmentModalData(calenderItem) {
|
||||
this.resourcesAssignmentModal.availableResources =
|
||||
await this.fetchSchedulableResourcesByCalender(
|
||||
calenderItem.kalender_id,
|
||||
);
|
||||
this.resourcesAssignmentModal.filteredAvailableResources = [
|
||||
...this.dropdownParsedAvailableResources,
|
||||
];
|
||||
|
||||
this.resourcesAssignmentModal.assignedResources =
|
||||
await this.fetchAssignedResourcesByCalender(calenderItem.kalender_id);
|
||||
this.resourcesAssignmentModal.selectedAvailableResource = null;
|
||||
this.resourcesAssignmentModal.areFormButtonsDisplayed = false;
|
||||
},
|
||||
async saveAssignedResourcesToCalendarItem(calenderItem, assignedResources) {
|
||||
let getSchedulableResourcesByCalendar = await this.$api.call(
|
||||
ApiOperationalResourceToCalender.storeResourcesToCalendarRelationship(
|
||||
calenderItem.kalender_id,
|
||||
assignedResources,
|
||||
),
|
||||
);
|
||||
if (getSchedulableResourcesByCalendar.meta.status === "success") {
|
||||
this.$fhcAlert.alertSuccess(
|
||||
this.$p.t("ui", "assigned_resources_save_success_message"),
|
||||
);
|
||||
await this.refreshResourcesAssignmentModalData(calenderItem);
|
||||
} else {
|
||||
this.$fhcAlert.alertError(
|
||||
this.$p.t("ui", "failed_assigned_resources_save_error_message"),
|
||||
);
|
||||
}
|
||||
|
||||
this.$refs.calendar.resetEventLoader();
|
||||
this.$refs.resourcesAssignmentModal.hide();
|
||||
},
|
||||
closeResourcesAssignmentModal() {
|
||||
this.resourcesAssignmentModal = {
|
||||
availableResources: [],
|
||||
filteredAvailableResources: [],
|
||||
selectedAvailableResource: null,
|
||||
assignedResources: [],
|
||||
areFormButtonsDisplayed: false,
|
||||
};
|
||||
},
|
||||
removeLecturer(uid) {
|
||||
if (uid == null) {
|
||||
for (const lect of this.lecturers) delete this.overlayCache[lect.uid];
|
||||
@@ -933,6 +756,10 @@ export default {
|
||||
this.$refs.calendar.resetEventLoader();
|
||||
}
|
||||
},
|
||||
handlePreviewRoleChange(role) {
|
||||
this.previewRole = role;
|
||||
this.$refs.calendar.resetEventLoader();
|
||||
},
|
||||
triggerSync() {
|
||||
this.$api
|
||||
.call(ApiKalender.sync())
|
||||
@@ -950,10 +777,13 @@ export default {
|
||||
const event = this.hoveredEvent;
|
||||
if (!event?.kalender_id) return;
|
||||
|
||||
if (this.$refs.parking.isParked(event.kalender_id)) {
|
||||
this.$refs.parking.unpark({ type: event.type, id: event.kalender_id });
|
||||
if (this.$refs.sidebar.isParked(event.kalender_id)) {
|
||||
this.$refs.sidebar.unpark({
|
||||
type: event.type,
|
||||
id: event.kalender_id,
|
||||
});
|
||||
} else {
|
||||
this.$refs.parking.park(null, {
|
||||
this.$refs.sidebar.park({
|
||||
type: "kalender",
|
||||
id: event.kalender_id,
|
||||
orig: event,
|
||||
@@ -970,7 +800,7 @@ export default {
|
||||
this.hoveredEvent = null;
|
||||
},
|
||||
focusSearchbar() {
|
||||
this.$refs.searchbar?.$refs?.input.focus();
|
||||
this.$refs.header?.focusSearchbar();
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
@@ -988,19 +818,6 @@ export default {
|
||||
handler() {
|
||||
this.$refs.calendar.resetEventLoader();
|
||||
},
|
||||
},
|
||||
"resourcesAssignmentModal.selectedAvailableResource": function (newVal) {
|
||||
if (!newVal) return;
|
||||
|
||||
this.resourcesAssignmentModal.assignedResources.push({
|
||||
betriebsmittel_kalender_id: null,
|
||||
betriebsmittel_id: newVal.data.betriebsmittel_id,
|
||||
beschreibung: newVal.data.beschreibung,
|
||||
anmerkung: "",
|
||||
isNoteTextareaShown: false,
|
||||
});
|
||||
|
||||
this.resourcesAssignmentModal.areFormButtonsDisplayed = true;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
@@ -1077,198 +894,41 @@ export default {
|
||||
this.studiengaenge_all = res.data;
|
||||
});
|
||||
},
|
||||
template: `
|
||||
template: /* html */`
|
||||
<div
|
||||
class="tempus"
|
||||
data-cy="tempus"
|
||||
>
|
||||
<keyboard-shortcuts :shortcuts="keyboardShortcuts" />
|
||||
<header class="navbar navbar-expand-lg navbar-dark bg-dark flex-md-nowrap p-0 shadow">
|
||||
<div class="col-md-4 col-lg-3 col-xl-2 d-flex align-items-center">
|
||||
<button
|
||||
class="btn btn-outline-light border-0 m-1 collapsed"
|
||||
type="button"
|
||||
data-bs-toggle="offcanvas"
|
||||
data-bs-target="#appMenu"
|
||||
aria-controls="appMenu"
|
||||
aria-expanded="false"
|
||||
:aria-label="$p.t('ui/toggle_nav')"
|
||||
>
|
||||
<span class="svg-icon svg-icon-apps"></span>
|
||||
</button>
|
||||
<a class="navbar-brand me-0" :href="tempusRoot">Tempus</a>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-outline-light border-0 d-md-none m-1 collapsed"
|
||||
type="button"
|
||||
data-bs-toggle="offcanvas"
|
||||
data-bs-target="#sidebarMenu"
|
||||
aria-controls="sidebarMenu"
|
||||
aria-expanded="false"
|
||||
:aria-label="$p.t('ui/toggle_nav')"
|
||||
>
|
||||
<span class="fa-solid fa-table-list"></span>
|
||||
</button>
|
||||
<core-searchbar
|
||||
ref="searchbar"
|
||||
:searchoptions="searchbaroptions"
|
||||
:searchfunction="searchfunction"
|
||||
class="searchbar position-relative w-100"
|
||||
show-btn-submit
|
||||
></core-searchbar>
|
||||
<div id="nav-user" class="dropdown">
|
||||
<button
|
||||
id="nav-user-btn"
|
||||
class="btn btn-link rounded-0 py-0"
|
||||
type="button"
|
||||
data-bs-toggle="dropdown"
|
||||
data-bs-target="#nav-user-menu"
|
||||
aria-expanded="false"
|
||||
aria-controls="nav-user-menu"
|
||||
>
|
||||
<img
|
||||
:src="avatarUrl"
|
||||
:alt="$p.t('profilUpdate/profilBild')"
|
||||
class="bg-light avatar rounded-circle border border-light"
|
||||
/>
|
||||
</button>
|
||||
<ul
|
||||
ref="navUserDropdown"
|
||||
class="dropdown-menu dropdown-menu-dark dropdown-menu-end rounded-0 text-center m-0"
|
||||
aria-labelledby="nav-user-btn"
|
||||
>
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
class="dropdown-item"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#configModal"
|
||||
>
|
||||
{{ $p.t('ui/settings') }}
|
||||
</button>
|
||||
</li>
|
||||
<li><hr class="dropdown-divider m-0"/></li>
|
||||
<li>
|
||||
<nav-language
|
||||
item-class="dropdown-item border-left-dark"
|
||||
/>
|
||||
</li>
|
||||
<li><hr class="dropdown-divider m-0"/></li>
|
||||
<li>
|
||||
<a class="dropdown-item" :href="logoutUrl">
|
||||
{{ $p.t('ui/logout') }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</header>
|
||||
<tempus-header
|
||||
ref="header"
|
||||
:tempus-root="tempusRoot"
|
||||
:avatar-url="avatarUrl"
|
||||
:logout-url="logoutUrl"
|
||||
:searchbaroptions="searchbaroptions"
|
||||
:searchfunction="searchfunction"
|
||||
/>
|
||||
<div class="container-fluid overflow-hidden heightfull">
|
||||
<div class="row h-100">
|
||||
<aside id="appMenu" class="bg-light offcanvas offcanvas-start col-md p-md-0 h-100">
|
||||
<div class="offcanvas-header">
|
||||
Tempus
|
||||
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" :aria-label="$p.t('ui/schliessen')"></button>
|
||||
</div>
|
||||
<div class="offcanvas-body">
|
||||
<app-menu app-identifier="tempus" />
|
||||
</div>
|
||||
</aside>
|
||||
<nav id="sidebarMenu" class="bg-light offcanvas offcanvas-start col-md p-md-0 h-100 d-flex flex-column">
|
||||
<div class="sidebar-icons d-flex flex-row align-items-start py-2 gap-1 ps-2">
|
||||
<button
|
||||
class="btn btn-outline-secondary"
|
||||
type="button"
|
||||
data-bs-toggle="offcanvas"
|
||||
data-bs-target="#verbandMenu"
|
||||
aria-controls="verbandMenu"
|
||||
aria-expanded="false"
|
||||
title="Verband"
|
||||
>
|
||||
<span class="fa-solid fa-university"></span>
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-outline-secondary"
|
||||
type="button"
|
||||
data-bs-toggle="offcanvas"
|
||||
data-bs-target="#verbandMenu"
|
||||
aria-controls="verbandMenu"
|
||||
aria-expanded="false"
|
||||
title="Verband"
|
||||
>
|
||||
<span class="fa-solid fa-door-open"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="px-2 py-1 w-100">
|
||||
<!--<Multiselect
|
||||
:model-value="visibleStatusValue"
|
||||
@update:model-value="val => toggleStatus(val.map(o => o.key))"
|
||||
option-label="label"
|
||||
:options="visibleStatusOptions"
|
||||
placeholder="Status filtern"
|
||||
:hide-selected="false"
|
||||
:show-toggle-all="false"
|
||||
class="w-100"
|
||||
/>-->
|
||||
|
||||
<div
|
||||
class="d-flex gap-1 py-1"
|
||||
data-cy="previewRoleOptionsHolder"
|
||||
>
|
||||
<button
|
||||
class="btn btn-sm"
|
||||
:class="previewRole === 'planer' ? 'btn-dark' : 'btn-outline-dark'"
|
||||
@click="previewRole = 'planer'; $refs.calendar.resetEventLoader()"
|
||||
>
|
||||
<i class="fa-solid fa-pen-ruler me-1"></i>Planer
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm"
|
||||
:class="previewRole === 'lektor' ? 'btn-primary' : 'btn-outline-primary'"
|
||||
@click="previewRole = 'lektor'; $refs.calendar.resetEventLoader()"
|
||||
>
|
||||
<i class="fa-solid fa-chalkboard-user me-1"></i>Lektor
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm"
|
||||
:class="previewRole === 'student' ? 'btn-success' : 'btn-outline-success'"
|
||||
@click="previewRole = 'student'; $refs.calendar.resetEventLoader()"
|
||||
>
|
||||
<i class="fa-solid fa-user-graduate me-1"></i>Student
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm btn-outline-danger"
|
||||
@click="triggerSync"
|
||||
>
|
||||
<i class="fa-solid fa-rotate me-1"></i>Sync
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<room-selection
|
||||
v-if="rooms.length"
|
||||
v-model:rooms="rooms"
|
||||
></room-selection>
|
||||
<verband-selection
|
||||
v-if="studiengaenge.length"
|
||||
v-model:studiengaenge="studiengaenge"
|
||||
:studiengaenge-all="studiengaenge_all"
|
||||
></verband-selection>
|
||||
<lecture-selection
|
||||
v-if="lecturers.length"
|
||||
:lecturers="lecturers"
|
||||
@remove="removeLecturer"
|
||||
></lecture-selection>
|
||||
<div class="d-flex flex-column flex-grow-1" style="min-height: 0">
|
||||
<parking-slot
|
||||
ref="parking"
|
||||
v-model:parked-keys="parkedKeys"
|
||||
></parking-slot>
|
||||
|
||||
<fhc-coursepicker ref="coursepicker" :studiengaenge="studiengaenge" @select-lecturer="setEmp" @select-kw="jumpToKw" :studiensemester="selectedStudiensemester"></fhc-coursepicker>
|
||||
|
||||
</div>
|
||||
<stv-studiensemester v-model:studiensemester-kurzbz="selectedStudiensemester"></stv-studiensemester>
|
||||
|
||||
</nav>
|
||||
<tempus-app-menu />
|
||||
<tempus-sidebar-menu
|
||||
ref="sidebar"
|
||||
:preview-role="previewRole"
|
||||
:rooms="rooms"
|
||||
:studiengaenge="studiengaenge"
|
||||
:studiengaenge-all="studiengaenge_all"
|
||||
:lecturers="lecturers"
|
||||
:selected-studiensemester="selectedStudiensemester"
|
||||
@update:preview-role="handlePreviewRoleChange"
|
||||
@update:rooms="rooms = $event"
|
||||
@update:studiengaenge="studiengaenge = $event"
|
||||
@update:parked-keys="parkedKeys = $event"
|
||||
@update:selected-studiensemester="selectedStudiensemester = $event"
|
||||
@remove-lecturer="removeLecturer"
|
||||
@select-lecturer="setEmp"
|
||||
@select-kw="jumpToKw"
|
||||
@sync="triggerSync"
|
||||
/>
|
||||
<main class="col-md-8 ms-sm-auto col-lg-9 col-xl-10">
|
||||
<fhc-calendar
|
||||
ref="calendar"
|
||||
@@ -1293,16 +953,10 @@ export default {
|
||||
</div>
|
||||
</div>
|
||||
<app-config ref="config" v-model="appconfig" :endpoints="configEndpoints"></app-config>
|
||||
<div id="verbandMenu" ref="verbandMenu" class="offcanvas offcanvas-start col-md p-md-0 h-100" tabindex="-1" data-cy="verbandMenu">
|
||||
<div class="offcanvas-header justify-content-end px-1 d-md-none">
|
||||
<h5 class="offcanvas-title" id="verbandMenuLabel">
|
||||
<i class="fa-solid fa-university me-2"></i>Verband
|
||||
</h5>
|
||||
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" :aria-label="$p.t('ui/schliessen')"></button>
|
||||
</div>
|
||||
<stv-verband :endpoint="endpoint" @select-verband="onSelectVerbandAndClose" class="col" style="height:0%"></stv-verband>
|
||||
</div>
|
||||
|
||||
<tempus-verband-menu
|
||||
:endpoint="endpoint"
|
||||
@select-verband="onSelectVerband"
|
||||
/>
|
||||
<bs-modal ref="raumModal" class="bootstrap-prompt">
|
||||
<template #title>Raumauswahl</template>
|
||||
<template #default>
|
||||
@@ -1321,94 +975,15 @@ export default {
|
||||
<p v-else class="text-muted mb-0">Keine freien Räume gefunden.</p>
|
||||
</template>
|
||||
</bs-modal>
|
||||
<bs-modal
|
||||
<resources-assignment-modal
|
||||
ref="resourcesAssignmentModal"
|
||||
@hideBsModal="closeResourcesAssignmentModal"
|
||||
class="bootstrap-prompt"
|
||||
data-cy="resourcesAssignmentModal"
|
||||
>
|
||||
<template #title>{{$p.t('ui', 'resource_assignment_modal_title')}}</template>
|
||||
<template #default>
|
||||
<div class="mb-5">
|
||||
<form-input
|
||||
v-if="resourcesAssignmentModal.availableResources.length"
|
||||
@itemSelect="(option) => { resourcesAssignmentModal.selectedAvailableResource = option.value; }"
|
||||
:label="$p.t('ui', 'available_resources_label')"
|
||||
:suggestions="resourcesAssignmentModal.filteredAvailableResources"
|
||||
:optionValue="(option) => option.value"
|
||||
:optionLabel="(option) => option.label"
|
||||
@complete="filterAvailableResources"
|
||||
dropdown
|
||||
forceSelection
|
||||
type="autocomplete"
|
||||
name="availableResources"
|
||||
:closeOnSelect="false"
|
||||
>
|
||||
</form-input>
|
||||
</div>
|
||||
<div>
|
||||
<div class="d-flex align-items-center justify-content-between mb-2">
|
||||
<h6 class="mb-2 mx-auto text-bold fw-1">{{$p.t('ui', 'assigned_resources_subtitle')}}</h6>
|
||||
</div>
|
||||
<div v-if="resourcesAssignmentModal.assignedResources.length" class="mb-4">
|
||||
<div
|
||||
v-for="resource in resourcesAssignmentModal.assignedResources"
|
||||
:key="resource.betriebsmittel_id"
|
||||
class=" shadow-sm p-2 mb-2 bg-body rounded"
|
||||
>
|
||||
<div class="d-flex justify-content-between align-items-center mb-1">
|
||||
<p class="m-0">{{ resource.beschreibung }}</p>
|
||||
<div class="d-flex justify-content-between align-items-center gap-2">
|
||||
<a href="#" @click.prevent="toggleAssignedResourceNoteInput(resource)" class="ms-auto"><i class="fa fa-edit text-primary"></i></a>
|
||||
<a href="#" @click.prevent="removeAssignedResource(resource)" class="ms-auto"><i class="fa fa-trash text-danger"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<form-input
|
||||
v-if="resource.isNoteTextareaShown"
|
||||
v-model="resource.anmerkung"
|
||||
@input="this.resourcesAssignmentModal.areFormButtonsDisplayed = true"
|
||||
:placeholder="$capitalize($p.t('global/anmerkung'))"
|
||||
:rows="1"
|
||||
class="flex-grow-1"
|
||||
type="textarea"
|
||||
name="anmerkung"
|
||||
>
|
||||
</form-input>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="d-flex align-items-center justify-content-center mb-2">
|
||||
<p class="text-muted mb-0">{{$p.t('ui', 'no_assigned_resources')}}</p>
|
||||
</div>
|
||||
<div v-if="resourcesAssignmentModal.areFormButtonsDisplayed" class="d-flex justify-content-end gap-2">
|
||||
<button type="button" class="btn btn-secondary" @click="refreshResourcesAssignmentModalData(resourcesAssignmentModal.calendar)">{{$p.t('ui', 'abbrechen')}}</button>
|
||||
<button type="button" class="btn btn-primary" @click="saveAssignedResourcesToCalendarItem(resourcesAssignmentModal.calendar, resourcesAssignmentModal.assignedResources)">{{$p.t('ui', 'speichern')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</bs-modal>
|
||||
<bs-modal ref="historyModel" class="bootstrap-prompt" dialogClass="modal-lg" data-cy="historyModal">
|
||||
<template #title>History</template>
|
||||
<template #default>
|
||||
<table v-if="historyEntries.length" class="table table-bordered table-hover">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Von</th>
|
||||
<th>Bis</th>
|
||||
<th>Status</th>
|
||||
<th>Ort</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="entry in historyEntries" :key="entry.id">
|
||||
<td>{{ entry.von }}</td>
|
||||
<td>{{ entry.bis }}</td>
|
||||
<td>{{ entry.status_kurzbz }}</td>
|
||||
<td>{{ entry.ort }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
</bs-modal>
|
||||
@save-finished="$refs.calendar.resetEventLoader()"
|
||||
/>
|
||||
<tags-assignment-modal
|
||||
ref="tagsAssignmentModal"
|
||||
@tags-changed="$refs.calendar.resetEventLoader()"
|
||||
/>
|
||||
<history-modal ref="historyModal" :entries="historyEntries" />
|
||||
<reservierung
|
||||
ref="reservierung"
|
||||
:rooms="rooms"
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import StvVerband from "../Stv/Studentenverwaltung/Verband.js";
|
||||
|
||||
export default {
|
||||
name: "TempusVerbandMenu",
|
||||
components: {
|
||||
StvVerband,
|
||||
},
|
||||
props: {
|
||||
endpoint: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ["select-verband"],
|
||||
methods: {
|
||||
onSelectVerband(payload) {
|
||||
this.$emit("select-verband", payload);
|
||||
bootstrap.Offcanvas.getOrCreateInstance(this.$refs.menu).hide();
|
||||
},
|
||||
},
|
||||
template: /* html */ `
|
||||
<div
|
||||
id="verbandMenu"
|
||||
ref="menu"
|
||||
class="offcanvas offcanvas-start col-md p-md-0 h-100"
|
||||
tabindex="-1"
|
||||
data-cy="verbandMenu"
|
||||
>
|
||||
<div class="offcanvas-header justify-content-end px-1 d-md-none">
|
||||
<h5 class="offcanvas-title" id="verbandMenuLabel">
|
||||
<i class="fa-solid fa-university me-2"></i>Verband
|
||||
</h5>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close text-reset"
|
||||
data-bs-dismiss="offcanvas"
|
||||
:aria-label="$p.t('ui/schliessen')"
|
||||
></button>
|
||||
</div>
|
||||
<stv-verband
|
||||
:endpoint="endpoint"
|
||||
class="col"
|
||||
style="height:0%"
|
||||
@select-verband="onSelectVerband"
|
||||
></stv-verband>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
@@ -12,6 +12,11 @@ export function useContextMenuActions(handlers)
|
||||
icon: 'fa-solid fa-table-list',
|
||||
action: handlers.openResourcesAssignmentModal
|
||||
},
|
||||
{
|
||||
label: 'Tags',
|
||||
icon: 'fa-solid fa-tags',
|
||||
action: handlers.openTagsModal
|
||||
},
|
||||
{
|
||||
label: 'Freischalten für Voransicht',
|
||||
icon: 'fa-solid fa-chalkboard-user',
|
||||
|
||||
@@ -100,6 +100,7 @@ require_once('dbupdate_3.4/76150_perm_other_lv_plan.php');
|
||||
require_once('dbupdate_3.4/68957_dashboard_bookmark_neue_Spalte_sort.php');
|
||||
require_once('dbupdate_3.4/68530_Dashboard_Cleanup.php');
|
||||
require_once('dbupdate_3.4/77375_ressourcen.php');
|
||||
require_once('dbupdate_3.4/78052_tempus_tag_events.php');
|
||||
// *** Pruefung und hinzufuegen der neuen Attribute und Tabellen
|
||||
echo '<H2>Pruefe Tabellen und Attribute!</H2>';
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
if (! defined('DB_NAME')) exit('No direct script access allowed');
|
||||
|
||||
$result = $db->db_query("SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'tbl_notizzuordnung'
|
||||
AND column_name = 'eindeutige_kalender_gruppen_id'");
|
||||
|
||||
if ($db->db_num_rows($result) === 0)
|
||||
{
|
||||
$qry = "ALTER TABLE public.tbl_notizzuordnung
|
||||
ADD COLUMN eindeutige_kalender_gruppen_id UUID NULL DEFAULT NULL";
|
||||
|
||||
if (!$db->db_query($qry))
|
||||
echo '<strong>public.tbl_notizzuordnung: '.$db->db_last_error().'</strong><br>';
|
||||
else
|
||||
echo '<br>public.tbl_notizzuordnung: eindeutige_kalender_gruppen_id column added';
|
||||
}
|
||||
@@ -61240,6 +61240,26 @@ I have been informed that I am under no obligation to consent to the transmissio
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'tags_assignment_modal_title',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Tag-Zuweisung',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Tag Assignment',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'lvplan',
|
||||
@@ -61260,6 +61280,66 @@ I have been informed that I am under no obligation to consent to the transmissio
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'assigned_tags_subtitle',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Zugewiesene Tags',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Assigned Tags',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'no_assigned_tags',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Keine zugewiesenen Tags',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'No Assigned Tags',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'tags',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Tags',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Tags',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
// generic tabulator phrases END
|
||||
);
|
||||
|
||||
|
||||
@@ -177,6 +177,17 @@ VALUES
|
||||
(51006, 31),
|
||||
(51004, 32);
|
||||
|
||||
-- Calendar setup for tags tests
|
||||
INSERT INTO lehre.tbl_kalender
|
||||
(von, bis, typ, status_kurzbz, vorgaenger_kalender_id, eindeutige_gruppen_id, insertamum, insertvon, updateamum, updatevon)
|
||||
VALUES
|
||||
(date_trunc('week', CURRENT_DATE) + INTERVAL '6 day' + TIME '16:55:00', date_trunc('week', CURRENT_DATE) + INTERVAL '6 day' + TIME '17:40:00', 'lehreinheit', 'live', NULL, 'ec1bd001-facf-4b21-a671-bcb2d4f037fd', now(), 'demoadmin', NULL, 'demoadmin');
|
||||
|
||||
INSERT INTO lehre.tbl_kalender_lehreinheit
|
||||
(lehreinheit_id, kalender_id)
|
||||
VALUES
|
||||
(51004, 33);
|
||||
|
||||
-- INSERT INTO public.tbl_variablenname
|
||||
-- (name, defaultwert)
|
||||
-- VALUES('ignore_resources_collisions', 'false');
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
INSERT INTO public.tbl_notiz_typ
|
||||
(typ_kurzbz, bezeichnung_mehrsprachig, beschreibung, automatisiert, aktiv, zuordnung, tag, style, vorrueckung, prioritaet/*, taglib*/)
|
||||
VALUES
|
||||
('prioone', '{Prio 1,Prio 1}', '', false, true, NULL, false, 'tag_rot', false, 1/*, NULL*/),
|
||||
('priotwo', '{Prio 2,Prio 2}', '', false, true, NULL, false, 'tag_gelb', false, 2/*, NULL*/);
|
||||
@@ -71,6 +71,20 @@ context("Tempus smoke tests", () => {
|
||||
tempusPage.getResourcesModal().should("be.visible");
|
||||
});
|
||||
|
||||
it("shows tags modal when selecting Tags from event context menu", () => {
|
||||
tempusPage.waitForCalendarToFinishLoading();
|
||||
tempusPage
|
||||
.getCalendarEventsByWeekdayAndStartTime("Sunday", "16:55:00")
|
||||
.should("have.length.greaterThan", 0);
|
||||
|
||||
tempusPage.getCalendarEventsByWeekdayAndStartTime("Sunday", "16:55:00").first().rightclick();
|
||||
tempusPage.getEventContextMenuOption("Tags").click({ force: true });
|
||||
waitForOk("@fetchTags");
|
||||
waitForOk("@fetchTagsByCalendar");
|
||||
|
||||
tempusPage.getTagsModal().should("be.visible");
|
||||
});
|
||||
|
||||
it("shows history modal when selecting History from event context menu", () => {
|
||||
tempusPage.waitForCalendarToFinishLoading();
|
||||
tempusPage.getCalendarEvents().should("have.length.greaterThan", 0);
|
||||
|
||||
@@ -72,6 +72,14 @@ class TempusPage {
|
||||
method: "GET",
|
||||
url: "**/tempus/OperationalResourceToCalenderAPI/getAssignedResourcesByCalender/**",
|
||||
}).as("fetchAssignedResources");
|
||||
cy.intercept({
|
||||
method: "GET",
|
||||
url: "**/tempus/Tags/getTags**",
|
||||
}).as("fetchTags");
|
||||
cy.intercept({
|
||||
method: "GET",
|
||||
url: "**/tempus/Tags/getTagsByCalendar/**",
|
||||
}).as("fetchTagsByCalendar");
|
||||
cy.intercept({
|
||||
method: "POST",
|
||||
url: "**/tempus/coursepicker/getByStg**",
|
||||
@@ -345,6 +353,8 @@ class TempusPage {
|
||||
cy
|
||||
.get("[data-cy='resourcesAssignmentModal']")
|
||||
.closest(".bootstrap-modal.show");
|
||||
getTagsModal = () =>
|
||||
cy.get("[data-cy='tagsAssignmentModal']").closest(".bootstrap-modal.show")
|
||||
getCalendarEventRoom = (id) =>
|
||||
this.getCalendarEventById(id).find("[data-cy='calendar-event-room']");
|
||||
getStundenrasterToggle = () =>
|
||||
|
||||
Reference in New Issue
Block a user