mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-10-24 04:39:36 +00:00
Merge branch 'feature-78052/tempus-tag-events' into feature-68298/Tempus_Grundstruktur
This commit is contained in:
@@ -140,6 +140,8 @@ $route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/emp/(:any)/stg/(
|
||||
// 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'));
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,6 @@ class Tag_Controller extends FHCAPI_Controller
|
||||
{
|
||||
$language = $this->_getLanguageIndex();
|
||||
$index_bezeichnung_mehrsprachig = $language - 1;
|
||||
|
||||
$id = $this->input->get('id');
|
||||
|
||||
if (is_array($readonly_tags) && !isEmptyArray($readonly_tags))
|
||||
@@ -123,7 +122,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();
|
||||
@@ -143,7 +171,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();
|
||||
@@ -152,7 +180,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);
|
||||
|
||||
@@ -23,6 +23,7 @@ class KalenderLib
|
||||
$this->_ci->load->model('ressource/Stunde_model', 'StundeModel');
|
||||
$this->_ci->load->model('organisation/Studiensemester_model', 'StudiensemesterModel');
|
||||
$this->_ci->load->model('system/Variable_model', 'VariableModel');
|
||||
$this->_ci->load->model('system/Sprache_model', 'SpracheModel');
|
||||
$this->_ci->load->model('organisation/Ferien_model', 'FerienModel');
|
||||
|
||||
|
||||
@@ -35,69 +36,75 @@ class KalenderLib
|
||||
|
||||
private function _getBasePlan($start_date, $end_date)
|
||||
{
|
||||
$language = $this->getLanguageIndex();
|
||||
$index_bezeichnung_mehrsprachig = $language - 1;
|
||||
|
||||
$end_date = date('Y-m-d', strtotime($end_date . ' +1 day'));
|
||||
|
||||
$this->_ci->KalenderModel->addSelect('tbl_kalender.kalender_id,
|
||||
tbl_kalender.eindeutige_kalender_gruppen_id,
|
||||
tbl_kalender.status_kurzbz,
|
||||
tbl_kalender.typ,
|
||||
tbl_kalender.von,
|
||||
tbl_kalender.bis,
|
||||
tbl_kalender_ort.location,
|
||||
tbl_kalender_ort.ort_kurzbz,
|
||||
tbl_lehreinheit.lehreinheit_id,
|
||||
tbl_lehreinheit.lehrveranstaltung_id,
|
||||
tbl_lehreinheit.lehrfach_id,
|
||||
tbl_lehreinheit.lehrform_kurzbz,
|
||||
tbl_lehrveranstaltung.oe_kurzbz,
|
||||
lehrfach.kurzbz AS lehrfach_kurzbz,
|
||||
lehrfach.bezeichnung AS lehrfach_bezeichnung,
|
||||
lehrfach.farbe,
|
||||
COALESCE(orginasator.uid, tbl_lehreinheitmitarbeiter.mitarbeiter_uid) as mitarbeiter_uid,
|
||||
COALESCE(reservierung_person.vorname, tbl_person.vorname) as vorname,
|
||||
COALESCE(reservierung_person.nachname, tbl_person.nachname) as nachname,
|
||||
COALESCE(reservierung_ma.kurzbz, tbl_mitarbeiter.kurzbz) AS ma_kurzbz,
|
||||
teilnehmergruppe.gruppe_kurzbz as teilnehmerg_grp,
|
||||
COALESCE (
|
||||
UPPER(tbl_studiengang.typ || tbl_studiengang.kurzbz) ||
|
||||
COALESCE(verbandgruppe.semester::varchar, \'\') ||
|
||||
COALESCE(verbandgruppe.verband::varchar, \'\') ||
|
||||
COALESCE(verbandgruppe.gruppe, \'\'),
|
||||
\'\') as verband_grp,
|
||||
tbl_kalender_event.beschreibung,
|
||||
tbl_kalender_event.titel,
|
||||
teilmitarbeiter.kurzbz as teilnehmer_kurzbz,
|
||||
teilperson.vorname as teilnehmer_vorname,
|
||||
teilperson.nachname as teilnehmer_nachname,
|
||||
teilbenutzer.uid as teilnehmer_uid,
|
||||
CASE
|
||||
WHEN tbl_lehreinheitgruppe.gruppe_kurzbz IS NULL THEN
|
||||
COALESCE(
|
||||
UPPER(le_studiengang.typ || le_studiengang.kurzbz) ||
|
||||
COALESCE(tbl_lehreinheitgruppe.semester::varchar, \'\') ||
|
||||
COALESCE(tbl_lehreinheitgruppe.verband::varchar, \'\') ||
|
||||
COALESCE(tbl_lehreinheitgruppe.gruppe, \'\'),
|
||||
\'\')
|
||||
ELSE tbl_lehreinheitgruppe.gruppe_kurzbz
|
||||
END AS lehreinheit_gruppe_bezeichnung,
|
||||
tbl_lehreinheitgruppe.gruppe_kurzbz as le_gruppe_kurzbz,
|
||||
tbl_lehreinheitgruppe.studiengang_kz as le_studiengang_kz,
|
||||
tbl_lehreinheitgruppe.semester as le_semester,
|
||||
tbl_lehreinheitgruppe.verband as le_verband,
|
||||
tbl_lehreinheitgruppe.gruppe as le_gruppe,
|
||||
le_gruppe.direktinskription as le_direktinskription,
|
||||
CASE
|
||||
WHEN vonstunde.stunde IS NOT NULL AND bisstunde.stunde IS NOT NULL
|
||||
THEN bisstunde.stunde - vonstunde.stunde + 1
|
||||
ELSE ROUND(
|
||||
COALESCE(
|
||||
(
|
||||
SELECT SUM(EXTRACT(EPOCH FROM (LEAST(stunde.ende, tbl_kalender.bis::time) - GREATEST(stunde.beginn, tbl_kalender.von::time))) / 60) / 45
|
||||
FROM lehre.tbl_stunde stunde
|
||||
WHERE stunde.beginn < tbl_kalender.bis::time AND stunde.ende > tbl_kalender.von::time)
|
||||
,0)
|
||||
,2
|
||||
) END AS verplante_stunden');
|
||||
tbl_kalender.eindeutige_kalender_gruppen_id,
|
||||
tbl_kalender.status_kurzbz,
|
||||
tbl_kalender.typ,
|
||||
tbl_kalender.von,
|
||||
tbl_kalender.bis,
|
||||
tbl_kalender_ort.location,
|
||||
tbl_kalender_ort.ort_kurzbz,
|
||||
tbl_lehreinheit.lehreinheit_id,
|
||||
tbl_lehreinheit.lehrveranstaltung_id,
|
||||
tbl_lehreinheit.lehrfach_id,
|
||||
tbl_lehreinheit.lehrform_kurzbz,
|
||||
tbl_lehrveranstaltung.oe_kurzbz,
|
||||
lehrfach.kurzbz AS lehrfach_kurzbz,
|
||||
lehrfach.bezeichnung AS lehrfach_bezeichnung,
|
||||
lehrfach.farbe,
|
||||
COALESCE(orginasator.uid, tbl_lehreinheitmitarbeiter.mitarbeiter_uid) as mitarbeiter_uid,
|
||||
COALESCE(reservierung_person.vorname, tbl_person.vorname) as vorname,
|
||||
COALESCE(reservierung_person.nachname, tbl_person.nachname) as nachname,
|
||||
COALESCE(reservierung_ma.kurzbz, tbl_mitarbeiter.kurzbz) AS ma_kurzbz,
|
||||
teilnehmergruppe.gruppe_kurzbz as teilnehmerg_grp,
|
||||
COALESCE (
|
||||
UPPER(tbl_studiengang.typ || tbl_studiengang.kurzbz) ||
|
||||
COALESCE(verbandgruppe.semester::varchar, \'\') ||
|
||||
COALESCE(verbandgruppe.verband::varchar, \'\') ||
|
||||
COALESCE(verbandgruppe.gruppe, \'\'),
|
||||
\'\') as verband_grp,
|
||||
tbl_kalender_event.beschreibung,
|
||||
tbl_kalender_event.titel,
|
||||
teilmitarbeiter.kurzbz as teilnehmer_kurzbz,
|
||||
teilperson.vorname as teilnehmer_vorname,
|
||||
teilperson.nachname as teilnehmer_nachname,
|
||||
teilbenutzer.uid as teilnehmer_uid,
|
||||
CASE
|
||||
WHEN tbl_lehreinheitgruppe.gruppe_kurzbz IS NULL THEN
|
||||
COALESCE(
|
||||
UPPER(le_studiengang.typ || le_studiengang.kurzbz) ||
|
||||
COALESCE(tbl_lehreinheitgruppe.semester::varchar, \'\') ||
|
||||
COALESCE(tbl_lehreinheitgruppe.verband::varchar, \'\') ||
|
||||
COALESCE(tbl_lehreinheitgruppe.gruppe, \'\'),
|
||||
\'\')
|
||||
ELSE tbl_lehreinheitgruppe.gruppe_kurzbz
|
||||
END AS lehreinheit_gruppe_bezeichnung,
|
||||
tbl_lehreinheitgruppe.gruppe_kurzbz as le_gruppe_kurzbz,
|
||||
tbl_lehreinheitgruppe.studiengang_kz as le_studiengang_kz,
|
||||
tbl_lehreinheitgruppe.semester as le_semester,
|
||||
tbl_lehreinheitgruppe.verband as le_verband,
|
||||
tbl_lehreinheitgruppe.gruppe as le_gruppe,
|
||||
le_gruppe.direktinskription as le_direktinskription,
|
||||
tag_data_agg.tags as tags,
|
||||
resource_data_agg.resources as resources,
|
||||
CASE
|
||||
WHEN vonstunde.stunde IS NOT NULL AND bisstunde.stunde IS NOT NULL
|
||||
THEN bisstunde.stunde - vonstunde.stunde + 1
|
||||
ELSE ROUND(
|
||||
COALESCE(
|
||||
(
|
||||
SELECT SUM(EXTRACT(EPOCH FROM (LEAST(stunde.ende, tbl_kalender.bis::time) - GREATEST(stunde.beginn, tbl_kalender.von::time))) / 60) / 45
|
||||
FROM lehre.tbl_stunde stunde
|
||||
WHERE stunde.beginn < tbl_kalender.bis::time AND stunde.ende > tbl_kalender.von::time)
|
||||
,0)
|
||||
,2
|
||||
) END AS verplante_stunden
|
||||
');
|
||||
|
||||
$this->_ci->KalenderModel->addJoin('lehre.tbl_kalender_lehreinheit', 'tbl_kalender.kalender_id = tbl_kalender_lehreinheit.kalender_id', 'LEFT');
|
||||
$this->_ci->KalenderModel->addJoin('lehre.tbl_kalender_event', 'tbl_kalender.kalender_id = tbl_kalender_event.kalender_id', 'LEFT');
|
||||
@@ -143,6 +150,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::varchar[])->>". $index_bezeichnung_mehrsprachig. " 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_kalender_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_kalender_gruppen_id',
|
||||
'LEFT'
|
||||
);
|
||||
// End of operational resources inclusion
|
||||
|
||||
|
||||
$this->_ci->KalenderModel->db->join('lehre.tbl_stunde vonstunde', 'tbl_kalender.von::time = vonstunde.beginn', 'LEFT', FALSE);
|
||||
$this->_ci->KalenderModel->db->join('lehre.tbl_stunde bisstunde', 'tbl_kalender.bis::time = bisstunde.ende', 'LEFT', FALSE);
|
||||
|
||||
@@ -169,6 +239,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) [
|
||||
'eindeutige_kalender_gruppen_id' => $row->eindeutige_kalender_gruppen_id,
|
||||
@@ -199,7 +271,9 @@ class KalenderLib
|
||||
'beschreibung' => isset($row->beschreibung) ? $row->beschreibung : '',
|
||||
'topic' => [],
|
||||
'collisions' => false,
|
||||
'has_assigned_resources' => isset($row->has_assigned_resources) ? $row->has_assigned_resources : false,
|
||||
'has_assigned_resources' => !empty($parsedResources),
|
||||
'resources' => is_array($parsedResources) ? $parsedResources : [],
|
||||
'tags' => isset($row->tags) ? $row->tags : [],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1912,4 +1986,11 @@ class KalenderLib
|
||||
return $allowed;
|
||||
}
|
||||
|
||||
}
|
||||
private function getLanguageIndex()
|
||||
{
|
||||
$this->_ci->SpracheModel->addSelect('index');
|
||||
$result = $this->_ci->SpracheModel->loadWhere(array('sprache' => getUserLanguage()));
|
||||
|
||||
return hasData($result) ? getData($result)[0]->index : 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
'filtercomponent' => true,
|
||||
'navigationcomponent' => true,
|
||||
'vuedatepicker11' => 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
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -76,7 +76,8 @@ export default {
|
||||
"drop",
|
||||
"resize",
|
||||
"event-hover",
|
||||
"event-unhover"
|
||||
"event-unhover",
|
||||
"open-reservierung"
|
||||
],
|
||||
|
||||
data() {
|
||||
@@ -301,4 +302,4 @@ export default {
|
||||
</div>
|
||||
</template>
|
||||
</fhc-calendar>`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,10 @@ export default {
|
||||
'deleted',
|
||||
],
|
||||
props: {
|
||||
isListItemShown: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
endpoint: {
|
||||
type: Object,
|
||||
required: true
|
||||
@@ -79,7 +83,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))
|
||||
},
|
||||
@@ -132,7 +136,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();
|
||||
}
|
||||
@@ -144,7 +148,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') {
|
||||
@@ -171,7 +175,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();
|
||||
},
|
||||
@@ -180,7 +184,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();
|
||||
},
|
||||
@@ -228,7 +232,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,108 @@
|
||||
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: ["language-changed"],
|
||||
methods: {
|
||||
focusSearchbar() {
|
||||
this.$refs.searchbar?.$refs?.input?.focus();
|
||||
},
|
||||
},
|
||||
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 @changed="$emit('language-changed', $event)" 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>
|
||||
`,
|
||||
};
|
||||
@@ -1,13 +1,19 @@
|
||||
export default {
|
||||
props:{
|
||||
props: {
|
||||
event: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed:{
|
||||
computed: {
|
||||
classes() {
|
||||
const classes = ['cis-renderer-lehreinheit-calendar-event', 'calendar-event-default', 'h-100', 'w-100', 'p-1'];
|
||||
const classes = [
|
||||
'cis-renderer-lehreinheit-calendar-event',
|
||||
'calendar-event-default',
|
||||
'h-100',
|
||||
'w-100',
|
||||
'p-1',
|
||||
];
|
||||
|
||||
if (this.event.collisions) {
|
||||
classes.push('calendar-event-collisions');
|
||||
@@ -15,6 +21,23 @@ export default {
|
||||
|
||||
return classes;
|
||||
},
|
||||
topicString() {
|
||||
return Array.isArray(this.event.topic)
|
||||
? this.event.topic.join(', ')
|
||||
: this.event.topic;
|
||||
},
|
||||
ortString() {
|
||||
let orte = [
|
||||
...(this.event.ort_kurzbz || []),
|
||||
this.event.location?.trim(),
|
||||
].filter(Boolean);
|
||||
return orte.join(', ');
|
||||
},
|
||||
gruppeString() {
|
||||
return Array.isArray(this.event.gruppe)
|
||||
? this.event.gruppe.map((gruppe) => gruppe.bezeichnung).join(', ')
|
||||
: this.event.gruppe;
|
||||
},
|
||||
statusIcon()
|
||||
{
|
||||
if (['planning', 'sync_preview'].includes(this.event.status_kurzbz))
|
||||
@@ -24,80 +47,119 @@ export default {
|
||||
else if (['live', 'to_delete_live'].includes(this.event.status_kurzbz))
|
||||
return 'fa-solid fa-user-graduate text-muted'
|
||||
},
|
||||
topicString() {
|
||||
return Array.isArray(this.event.topic) ? this.event.topic.join(', ') : this.event.topic;
|
||||
},
|
||||
ortString() {
|
||||
let orte = [...(this.event.ort_kurzbz || []), this.event.location?.trim()].filter(Boolean)
|
||||
return orte.join(', ')
|
||||
},
|
||||
gruppeString() {
|
||||
return Array.isArray(this.event.gruppe)
|
||||
? this.event.gruppe.map(gruppe => gruppe.bezeichnung).join(', ')
|
||||
: this.event.gruppe;
|
||||
},
|
||||
tooltipString() {
|
||||
const tooltipArray = [];
|
||||
|
||||
tooltipArray.push([
|
||||
this.$p.t('global/uhrzeit'),
|
||||
[this.start, this.end].join(' - ')
|
||||
].join(": "));
|
||||
tooltipArray.push(
|
||||
[this.$p.t('global/uhrzeit'), [this.start, this.end].join(' - ')].join(
|
||||
': ',
|
||||
),
|
||||
);
|
||||
|
||||
tooltipArray.push([
|
||||
this.$p.t('profilUpdate/topic'),
|
||||
this.topicString
|
||||
].join(": "));
|
||||
tooltipArray.push(
|
||||
[this.$p.t('profilUpdate/topic'), this.topicString].join(': '),
|
||||
);
|
||||
|
||||
tooltipArray.push([
|
||||
this.$p.t('person/ort'),
|
||||
this.ortString
|
||||
].join(": "));
|
||||
tooltipArray.push([this.$p.t('person/ort'), this.ortString].join(': '));
|
||||
|
||||
if (this.gruppeString) {
|
||||
tooltipArray.push([
|
||||
this.$p.t('lehre/gruppe'),
|
||||
this.gruppeString
|
||||
].join(": "));
|
||||
tooltipArray.push(
|
||||
[this.$p.t('lehre/gruppe'), this.gruppeString].join(': '),
|
||||
);
|
||||
}
|
||||
|
||||
if (Array.isArray(this.event.lektor) && this.event.lektor.length > 0) {
|
||||
if (this.event.lektor.length > 3) {
|
||||
tooltipArray.push([
|
||||
this.$p.t('lehre/lektor'),
|
||||
this.event.lektor.slice(0, 3).map(lektor => lektor.kurzbz).join("\n")
|
||||
+ "\n" + this.$p.t('lehre/weitereLektoren', [this.event.lektor.length - 3])
|
||||
].join(": "));
|
||||
tooltipArray.push(
|
||||
[
|
||||
this.$p.t('lehre/lektor'),
|
||||
this.event.lektor
|
||||
.slice(0, 3)
|
||||
.map((lektor) => lektor.kurzbz)
|
||||
.join('\n') +
|
||||
'\n' +
|
||||
this.$p.t('lehre/weitereLektoren', [
|
||||
this.event.lektor.length - 3,
|
||||
]),
|
||||
].join(': '),
|
||||
);
|
||||
} else {
|
||||
tooltipArray.push([
|
||||
this.$p.t('lehre/lektor'),
|
||||
this.event.lektor.map(lektor => lektor.kurzbz).join("\n")
|
||||
].join(": "));
|
||||
tooltipArray.push(
|
||||
[
|
||||
this.$p.t('lehre/lektor'),
|
||||
this.event.lektor.map((lektor) => lektor.kurzbz).join('\n'),
|
||||
].join(': '),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return tooltipArray.join("\n");
|
||||
return tooltipArray.join('\n');
|
||||
},
|
||||
start() {
|
||||
return luxon.Duration
|
||||
.fromISOTime(this.event.beginn)
|
||||
.toISOTime({ suppressSeconds: true });
|
||||
return luxon.Duration.fromISOTime(this.event.beginn).toISOTime({
|
||||
suppressSeconds: true,
|
||||
});
|
||||
},
|
||||
end() {
|
||||
return luxon.Duration
|
||||
.fromISOTime(this.event.ende)
|
||||
.toISOTime({ suppressSeconds: true });
|
||||
}
|
||||
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*/`
|
||||
template: /*html*/ `
|
||||
<div
|
||||
:class="classes"
|
||||
class="position-relative"
|
||||
@wheel.stop
|
||||
>
|
||||
<div class="position-absolute top-0 start-0 m-1">
|
||||
<div class="position-absolute top-0 start-0 m-1 d-flex gap-1" >
|
||||
<i :class="statusIcon"></i>
|
||||
<i class="fa-solid fa-table-list text-muted" v-if="event.has_assigned_resources"></i>
|
||||
<i
|
||||
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 class="position-absolute bottom-0 start-0 m-1">
|
||||
{{event.verplante_stunden}}
|
||||
@@ -131,4 +193,4 @@ export default {
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -53,11 +53,33 @@ 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) {
|
||||
return numberPadding(number);
|
||||
},
|
||||
methodFormatDate: function (d) {
|
||||
return formatDate(d);
|
||||
},
|
||||
@@ -78,7 +100,7 @@ export default {
|
||||
});
|
||||
}
|
||||
},
|
||||
template: `
|
||||
template: /*html*/`
|
||||
<div>
|
||||
<h5>
|
||||
{{$p.t('lvinfo','lehrveranstaltungsinformationen')}}
|
||||
@@ -143,9 +165,36 @@ 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>
|
||||
|
||||
<lv-menu :containerStyles="['p-0']" :rowStyles="['m-0']" v-if="lvMenu.length" :menu="lvMenu" />
|
||||
</div>`,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,9 +52,6 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
mehtodNumberPadding: function (number) {
|
||||
return numberPadding(number);
|
||||
},
|
||||
methodFormatDate: function (d) {
|
||||
return formatDate(d);
|
||||
},
|
||||
@@ -133,4 +130,4 @@ export default {
|
||||
</table>
|
||||
|
||||
</div>`,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,190 @@
|
||||
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,
|
||||
},
|
||||
raumvorschlagPreview: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
raumvorschlagLoading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: [
|
||||
'update:previewRole',
|
||||
'update:rooms',
|
||||
'update:studiengaenge',
|
||||
'update:parkedKeys',
|
||||
'update:selectedStudiensemester',
|
||||
'remove-lecturer',
|
||||
'select-lecturer',
|
||||
'select-kw',
|
||||
'sync',
|
||||
'preview-raumvorschlag',
|
||||
],
|
||||
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);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
courseLecturers() {
|
||||
if (!this.lecturers.length) return [];
|
||||
return this.lecturers
|
||||
.filter((lecture) => lecture.showCoursePicker)
|
||||
.map((lecture) => lecture.uid);
|
||||
},
|
||||
},
|
||||
template: /* html */ `
|
||||
<nav
|
||||
id="sidebarMenu"
|
||||
class="bg-light offcanvas offcanvas-start col-md p-md-0 h-100 w-100"
|
||||
>
|
||||
<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)"
|
||||
@select-kw="$emit('select-kw', $event)"
|
||||
></parking-slot>
|
||||
|
||||
<fhc-coursepicker
|
||||
ref="coursepicker"
|
||||
:studiengaenge="studiengaenge"
|
||||
:studiensemester="selectedStudiensemester"
|
||||
:lecturers="courseLecturers"
|
||||
:preview-lehreinheit-id="raumvorschlagPreview?.lehreinheit_id ?? null"
|
||||
:preview-loading="raumvorschlagLoading"
|
||||
@preview-raumvorschlag="$emit('preview-raumvorschlag', $event)"
|
||||
@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_kalender_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_kalender_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_kalender_gruppen_id"
|
||||
ref="tagComponent"
|
||||
:is-list-item-shown="false"
|
||||
:endpoint="tagEndpoint"
|
||||
:values="[calendar.eindeutige_kalender_gruppen_id]"
|
||||
zuordnung_typ="eindeutige_kalender_gruppen_id"
|
||||
@added="handleCalendarTagChange"
|
||||
@deleted="handleCalendarTagChange"
|
||||
@updated="handleCalendarTagChange"
|
||||
/>
|
||||
`,
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
import BaseTreemenu from '../Base/Treemenu.js';
|
||||
|
||||
export default {
|
||||
name: 'TempusVerbandMenu',
|
||||
components: {
|
||||
BaseTreemenu,
|
||||
},
|
||||
emits: ['select-verband-and-close'],
|
||||
methods: {
|
||||
onSelectVerbandAndClose(payload) {
|
||||
this.$emit('select-verband-and-close', payload);
|
||||
},
|
||||
},
|
||||
template: /* html */ `
|
||||
<div id="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>
|
||||
<base-treemenu config="tempus" @select-entry="onSelectVerbandAndClose" class="col" style="height:0%"></base-treemenu>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
@@ -18,6 +18,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',
|
||||
|
||||
@@ -99,6 +99,8 @@ 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');
|
||||
require_once('dbupdate_3.4/78318_rename_column_of_eindeutige_gruppen_id_to_eindeutige_kalender_gruppen_id_in_tbl_kalender.php');
|
||||
require_once('dbupdate_3.4/78318_rename_column_of_eindeutige_gruppen_id_to_eindeutige_kalender_gruppen_id_in_tbl_kalender.php');
|
||||
require_once('dbupdate_3.4/75959_StudVw_Automatische_Tags.php');
|
||||
require_once('dbupdate_3.4/76160_lvv_favorites.php');
|
||||
|
||||
@@ -257,7 +257,7 @@ if(!$result = @$db->db_query("SELECT status_kurzbz_max FROM lehre.tbl_kalender L
|
||||
|
||||
UPDATE lehre.tbl_kalender_status
|
||||
SET status_kurzbz = 'to_delete',
|
||||
bezeichnung = 'to_delete',
|
||||
bezeichnung = 'to_delete'
|
||||
WHERE status_kurzbz = 'todelete';
|
||||
UPDATE lehre.tbl_kalender_status SET sort = 9 WHERE status_kurzbz = 'deleted';
|
||||
UPDATE lehre.tbl_kalender_status SET sort = 10 WHERE status_kurzbz = 'archived';
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
@@ -62398,6 +62398,86 @@ 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' => '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
|
||||
array(
|
||||
'app' => 'core',
|
||||
|
||||
@@ -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_kalender_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