Merge branch 'master' into feature-78257/StudVW_Und_Tags_Finetuning

This commit is contained in:
Harald Bamberger
2026-08-19 15:07:07 +02:00
22 changed files with 367 additions and 104 deletions
+1
View File
@@ -1,5 +1,6 @@
documents/
vendor/
public/vendor
/nbproject/
.vscode
+2
View File
@@ -91,11 +91,13 @@ $route['api/frontend/v1/treemenudata/stv/stdsem/(:any)/inout/1/incoming/1'] = 'a
$route['api/frontend/v1/treemenudata/stv/stdsem/(:any)/inout/1/outgoing/1'] = 'api/frontend/v1/stv/students/getOutgoing/$1';
$route['api/frontend/v1/treemenudata/stv/stdsem/(:any)/inout/1/shared_studies/1'] = 'api/frontend/v1/stv/students/getGemeinsamestudien/$1';
$route['api/frontend/v1/treemenudata/stv/stdsem/:any/stg/(:any)/prestudent/1/stdsem/(:any)/:any/1/:any/1/(:any)/1'] = 'api/frontend/v1/stv/students/getPrestudents/$1/$2/$3';
$route['api/frontend/v1/treemenudata/stv/stdsem/:any/stg/(:any)/prestudent/1/stdsem/(:any)/:any/1/(:any)/1'] = 'api/frontend/v1/stv/students/getPrestudents/$1/$2/$3';
$route['api/frontend/v1/treemenudata/stv/stdsem/:any/stg/(:any)/prestudent/1/stdsem/(:any)/(:any)/1'] = 'api/frontend/v1/stv/students/getPrestudents/$1/$2/$3';
$route['api/frontend/v1/treemenudata/stv/stdsem/:any/stg/(:any)/prestudent/1/stdsem/(:any)'] = 'api/frontend/v1/stv/students/getPrestudents/$1/$2';
$route['api/frontend/v1/treemenudata/stv/stdsem/:any/stg/(:any)/prestudent/1'] = 'api/frontend/v1/stv/students/getPrestudents/$1';
$route['api/frontend/v1/treemenudata/stv/stdsem/:any/stg/(:any)/orgform/(:any)/prestudent/1/stdsem/(:any)/:any/1/:any/1/(:any)/1'] = 'api/frontend/v1/stv/students/getPrestudentsOrgform/$1/$2/$3/$4';
$route['api/frontend/v1/treemenudata/stv/stdsem/:any/stg/(:any)/orgform/(:any)/prestudent/1/stdsem/(:any)/:any/1/(:any)/1'] = 'api/frontend/v1/stv/students/getPrestudentsOrgform/$1/$2/$3/$4';
$route['api/frontend/v1/treemenudata/stv/stdsem/:any/stg/(:any)/orgform/(:any)/prestudent/1/stdsem/(:any)/(:any)/1'] = 'api/frontend/v1/stv/students/getPrestudentsOrgform/$1/$2/$3/$4';
$route['api/frontend/v1/treemenudata/stv/stdsem/:any/stg/(:any)/orgform/(:any)/prestudent/1/stdsem/(:any)'] = 'api/frontend/v1/stv/students/getPrestudentsOrgform/$1/$2/$3';
@@ -62,6 +62,8 @@ class OtherLvPlan extends FHCAPI_Controller
"foto" => $profileData->data->foto,
"vorname" => $profileData->data->vorname,
"nachname" => $profileData->data->nachname,
"titel" => $profileData->data->titel,
"postnomen" => $profileData->data->postnomen,
],
];
+1 -1
View File
@@ -77,7 +77,7 @@ class StudentListLib
CASE
WHEN pls.status_kurzbz IN ('Interessent','Aufgenommener','Bewerber','Wartender')
THEN pls.ausbildungssemester
ELSE s.semester
ELSE v.semester
END AS semester_berechnet
");
$this->addSelect(
@@ -13,6 +13,7 @@ class Gehaltsbestandteil extends AbstractBestandteil implements \JsonSerializabl
protected $dienstverhaeltnis_id;
protected $vertragsbestandteil_id;
protected $gehaltstyp_kurzbz;
protected $gehaltsanpassungtyp_kurzbz = '';
protected $von;
protected $bis;
protected $anmerkung;
@@ -21,6 +22,7 @@ class Gehaltsbestandteil extends AbstractBestandteil implements \JsonSerializabl
protected $valorisierungssperre;
protected $valorisierung;
protected $auszahlungen;
protected $gehaltstyp_bezeichnung;
protected $insertamum;
protected $insertvon;
@@ -39,6 +41,10 @@ class Gehaltsbestandteil extends AbstractBestandteil implements \JsonSerializabl
isset($data->dienstverhaeltnis_id) && $this->setDienstverhaeltnis_id($data->dienstverhaeltnis_id);
isset($data->vertragsbestandteil_id) && $this->setVertragsbestandteil_id($data->vertragsbestandteil_id);
isset($data->gehaltstyp_kurzbz) && $this->setGehaltstyp_kurzbz($data->gehaltstyp_kurzbz);
//isset($data->gehaltsanpassungtyp_kurzbz) && $this->setGehaltsanpassungtyp_kurzbz($data->gehaltsanpassungtyp_kurzbz);
if (!empty($data->gehaltsanpassungtyp_kurzbz)) {
$this->setGehaltsanpassungtyp_kurzbz($data->gehaltsanpassungtyp_kurzbz);
}
isset($data->von) && $this->setVon($data->von);
isset($data->bis) && $this->setBis($data->bis);
isset($data->anmerkung) && $this->setAnmerkung($data->anmerkung);
@@ -47,7 +53,8 @@ class Gehaltsbestandteil extends AbstractBestandteil implements \JsonSerializabl
isset($data->valorisierungssperre) && $this->setValorisierungssperre($data->valorisierungssperre);
isset($data->valorisierung) && $this->setValorisierung($data->valorisierung);
isset($data->auszahlungen) && $this->setAuszahlungen($data->auszahlungen);
isset($data->gehaltstyp_bezeichnung) && $this->setGehaltstyp_bezeichnung($data->gehaltstyp_bezeichnung);
isset($data->insertamum) && $this->setInsertamum($data->insertamum);
isset($data->insertvon) && $this->setInsertvon($data->insertvon);
isset($data->updateamum) && $this->setUpdateamum($data->updateamum);
@@ -75,6 +82,11 @@ class Gehaltsbestandteil extends AbstractBestandteil implements \JsonSerializabl
return $this->gehaltstyp_kurzbz;
}
public function getGehaltsanpassungtyp_kurzbz()
{
return $this->gehaltsanpassungtyp_kurzbz;
}
public function getVon()
{
return $this->von;
@@ -178,6 +190,13 @@ class Gehaltsbestandteil extends AbstractBestandteil implements \JsonSerializabl
return $this;
}
public function setGehaltsanpassungtyp_kurzbz($gehaltsanpassungtyp_kurzbz)
{
$this->markDirty('gehaltsanpassungtyp_kurzbz', $this->gehaltsanpassungtyp_kurzbz, $gehaltsanpassungtyp_kurzbz);
$this->gehaltsanpassungtyp_kurzbz = $gehaltsanpassungtyp_kurzbz;
return $this;
}
public function setVon($von)
{
$this->markDirty('von', $this->von, $von);
@@ -206,6 +225,10 @@ class Gehaltsbestandteil extends AbstractBestandteil implements \JsonSerializabl
$grundbetrag = number_format($grundbetrag, 2, '.', '');
}
$this->markDirty('grundbetrag', $this->grundbetrag, $grundbetrag);
if(in_array('grundbetrag', $this->modifiedcolumns))
{
$this->setBetrag_valorisiert($grundbetrag);
}
$this->grundbetrag = $grundbetrag;
return $this;
}
@@ -242,6 +265,13 @@ class Gehaltsbestandteil extends AbstractBestandteil implements \JsonSerializabl
return $this;
}
public function setGehaltstyp_bezeichnung($gehaltstyp_bezeichnung)
{
$this->markDirty('gehaltstyp_bezeichnung', $this->gehaltstyp_bezeichnung, $gehaltstyp_bezeichnung);
$this->gehaltstyp_bezeichnung = $gehaltstyp_bezeichnung;
return $this;
}
public function setInsertamum($insertamum)
{
$this->markDirty('insertamum', $this->insertamum, $insertamum);
@@ -284,6 +314,7 @@ class Gehaltsbestandteil extends AbstractBestandteil implements \JsonSerializabl
'dienstverhaeltnis_id' => $this->getDienstverhaeltnis_id(),
'vertragsbestandteil_id' => $this->getVertragsbestandteil_id(),
'gehaltstyp_kurzbz' => $this->getGehaltstyp_kurzbz(),
'gehaltsanpassungtyp_kurzbz' => $this->getGehaltsanpassungtyp_kurzbz(),
'von' => $this->getVon(),
'bis' => $this->getBis(),
'anmerkung' => $this->getAnmerkung(),
@@ -312,6 +343,7 @@ class Gehaltsbestandteil extends AbstractBestandteil implements \JsonSerializabl
dienstverhaeltnis_id: {$this->getDienstverhaeltnis_id()}
vertragsbestandteil_id: {$this->getVertragsbestandteil_id()}
gehaltstyp_kurzbz: {$this->getGehaltstyp_kurzbz()}
gehaltsanpassungtyp_kurzbz: {$this->getGehaltsanpassungtyp_kurzbz()}
von: {$this->getVon()}
bis: {$this->getBis()}
anmerkung: {$this->getAnmerkung()}
@@ -457,6 +457,7 @@ class Studiengang_model extends DB_Model
*/
public function getLeitung($studiengang_kz = null)
{
$this->addDistinct();
$this->addSelect('uid, studiengang_kz, oe_kurzbz, vorname, nachname, email, titelpre, titelpost, alias');
$this->addJoin('public.tbl_benutzerfunktion', 'oe_kurzbz');
$this->addJoin('public.tbl_benutzer', 'uid');
@@ -41,6 +41,7 @@ with gbt as (
gb.anmerkung,
gb.dienstverhaeltnis_id,
gb.gehaltstyp_kurzbz,
gb.gehaltsanpassungtyp_kurzbz,
gb.valorisierungssperre,
gb.valorisierung,
gb.grundbetrag as grund_betrag_decrypted,
@@ -76,11 +77,13 @@ select
gbt.anmerkung,
gbt.dienstverhaeltnis_id,
gbt.gehaltstyp_kurzbz,
gbt.gehaltsanpassungtyp_kurzbz,
gbt.valorisierungssperre,
gbt.valorisierung,
gbt.grund_betrag_decrypted,
gbt.betrag_val_decrypted,
gt.bezeichnung as gehaltstyp_bezeichnung,
ga.bezeichnung as gehaltsanpassungtyp_bezeichnung,
vb.vertragsbestandteiltyp_kurzbz,
bf.funktion_kurzbz,
bf.oe_kurzbz,
@@ -93,6 +96,8 @@ from
gbt
LEFT JOIN
hr.tbl_gehaltstyp gt using(gehaltstyp_kurzbz)
LEFT JOIN
hr.tbl_gehaltsanpassungtyp ga using(gehaltsanpassungtyp_kurzbz)
LEFT JOIN
hr.tbl_vertragsbestandteil vb using(vertragsbestandteil_id)
LEFT JOIN
@@ -177,7 +182,8 @@ LEFT JOIN
$stichtagclause .= ')';
}
$this->addSelect('*');
$this->addSelect('tbl_gehaltsbestandteil.*, tbl_gehaltstyp.bezeichnung AS gehaltstyp_bezeichnung');
$this->addJoin('hr.tbl_gehaltstyp', 'gehaltstyp_kurzbz');
$where = <<<EOSQL
dienstverhaeltnis_id = {$this->escape($dienstverhaeltnis_id)}
{$stichtagclause}
@@ -202,6 +208,7 @@ SELECT
g.dienstverhaeltnis_id,
g.vertragsbestandteil_id,
g.gehaltstyp_kurzbz,
g.gehaltsanpassungtyp_kurzbz,
g.von,
g.bis,
g.anmerkung,
@@ -213,9 +220,11 @@ SELECT
g.updateamum,
g.updatevon,
g.valorisierung,
g.auszahlungen
g.auszahlungen,
gt.bezeichnung AS gehaltstyp_bezeichnung
FROM
hr.tbl_gehaltsbestandteil g
hr.tbl_gehaltsbestandteil g
JOIN hr.tbl_gehaltstyp gt USING (gehaltstyp_kurzbz)
LEFT JOIN
hr.tbl_valorisierung_historie vh ON vh.gehaltsbestandteil_id = g.gehaltsbestandteil_id AND vh.valorisierungsdatum = (
SELECT
@@ -263,7 +272,7 @@ EOSQL;
// Note: replaced gb.betrag_valorisiert with vh.betrag_valorisiert!
$qry = "
SELECT
gb.gehaltsbestandteil_id,gb.dienstverhaeltnis_id,gb.vertragsbestandteil_id,gb.gehaltstyp_kurzbz,
gb.gehaltsbestandteil_id,gb.dienstverhaeltnis_id,gb.vertragsbestandteil_id,gb.gehaltstyp_kurzbz,gb.gehaltsanpassungtyp_kurzbz,
gb.von,gb.bis,gb.anmerkung,gb.grundbetrag as grundbetrag,gb.valorisierungssperre,gb.insertamum,
gb.insertvon,gb.updateamum,gb.updatevon,gb.valorisierung,gb.auszahlungen,
vh.valorisierungsdatum, vh.betrag_valorisiert as betrag_valorisiert
+3
View File
@@ -174,6 +174,9 @@ export default {
>
{{ $p.t('lehre/noLvFound') }}
</div>
<div v-else-if="!renderers || !renderers[event.type]" class="placeholder-glow">
<span class="placeholder col-12"></span>
</div>
<div
v-else
:class="'event-type-' + event.type + ' ' + mode + 'PageContainer'"
+3
View File
@@ -79,6 +79,9 @@ export default {
>
{{ $p.t('lehre/noLvFound') }}
</div>
<div v-else-if="!renderers || !renderers[event.type]" class="placeholder-glow">
<span class="placeholder col-12"></span>
</div>
<component
v-else-if="mode == 'eventheader'"
:is="renderers[event.type]?.modalTitle"
+27 -8
View File
@@ -32,7 +32,7 @@ export default {
listGroup: [],
rangeIntervalFirst: null,
otherPersonData: {
fullName: "",
fullNameWithTitle: "",
photo: "",
},
timezone: FHC_JS_DATA_STORAGE_OBJECT.timezone,
@@ -210,10 +210,17 @@ export default {
this.isOtherPersonMitarbeiter =
!!viewData?.user_data?.is_mitarbeiter;
this.isOtherPersonStudent = !!viewData?.user_data?.is_student;
this.otherPersonData.fullName =
viewData?.user_data?.vorname +
" " +
viewData?.user_data?.nachname;
let fullNameWithTitleFragments = [
viewData?.user_data?.titel,
viewData?.user_data?.vorname,
viewData?.user_data?.nachname,
viewData?.user_data?.postnomen,
];
fullNameWithTitleFragments = fullNameWithTitleFragments.filter(
(fragment) => !!fragment,
);
this.otherPersonData.fullNameWithTitle =
fullNameWithTitleFragments.join(" ");
this.otherPersonData.photo = viewData?.user_data?.foto;
},
async redirectToMyLvPlanIfAuthUid() {
@@ -225,12 +232,21 @@ export default {
this.$router.push({ name: "MyLvPlan" });
}
},
copyFullNameWithTitleToClipboard() {
navigator.clipboard.writeText(
this.otherPersonData.fullNameWithTitle,
);
this.$fhcAlert.alertSuccess(
this.$p.t("profil/name_title_clipboard_copy_confirmation"),
);
},
},
async created() {
await this.redirectToMyLvPlanIfAuthUid();
await this.fetchViewData();
await this.$p.loadCategory(["profil"]);
},
template: `
template: /*html*/ `
<div class="d-flex flex-column h-100">
<h2>
<div class="d-flex flex-row justify-content-between align-items-center">
@@ -238,8 +254,11 @@ export default {
{{ $p.t('lehre/stundenplan') + (studiensemester_kurzbz ? " " + studiensemester_kurzbz : "") }}
</span>
<div @click="this.$router.push({name: 'ProfilView', params: {uid: propsViewData.otherUid}})" type="button" class="d-flex flex-row align-items-center gap-3">
<span v-if="otherPersonData.fullName?.length">
{{ otherPersonData.fullName }}
<span v-if="otherPersonData.fullNameWithTitle?.length">
{{ otherPersonData.fullNameWithTitle }}
<span @click.stop="copyFullNameWithTitleToClipboard()" :title="$p.t('LvPlan/copy')" class="fs-4 h-100">
<i class="fa-regular fa-copy"></i>
</span>
</span>
<img v-if="otherPersonData.photo?.length" alt="profile picture" class=" img-thumbnail " style=" max-height:60px; " :src="get_image_base64_src"/>
</div>
@@ -67,6 +67,21 @@ export default {
return res;
},
},
methods: {
copyFullNameWithTitleToClipboard() {
let fragments = [
this.$props.data.Titel,
this.$props.data.Vorname,
this.$props.data.Nachname,
this.$props.data.Postnomen,
];
fragments = fragments.filter((fragment) => !!fragment);
navigator.clipboard.writeText(fragments.join(" "));
this.$fhcAlert.alertSuccess(
this.$p.t("profil/name_title_clipboard_copy_confirmation"),
);
},
},
template: /*html*/ `
<div class="card h-100">
@@ -124,6 +139,16 @@ export default {
<span class="form-underline-content">{{wert?wert:'-'}} </span>
</div>
</div>
<a
@click="copyFullNameWithTitleToClipboard()"
href="#"
class="col-12 fhc-link-color d-flex flex-row gap-2 align-items-center justify-content-center mt-3"
>
<span>
<i class="fa-regular fa-copy ms-2 text-decoration-none"></i>
</span>
<span>{{ $p.t("profil/name_title_copy") }}</span>
</a>
</div>
</div>
</div>
@@ -51,6 +51,16 @@ export default {
return numberPadding(this.event.ende.getHours()) + ":" + numberPadding(this.event.ende.getMinutes());
}
},
watch: {
event: {
async handler() {
if (this.$props.event.type == "lehreinheit") {
await this.getLvMenu();
}
},
deep: true,
},
},
methods: {
mehtodNumberPadding: function (number) {
return numberPadding(number);
@@ -58,21 +68,31 @@ export default {
methodFormatDate: function (d) {
return formatDate(d);
},
async getLvMenu() {
const semesterResponse = await this.$api.call(
ApiLvPlan.getLehreinheitStudiensemester(
Array.isArray(this.event.lehreinheit_id)
? this.event.lehreinheit_id[0]
: this.event.lehreinheit_id,
),
);
if (semesterResponse.meta.status !== "success") return;
const studiensemesterKurzbz = semesterResponse.data;
const lvMenuResponse = await this.$api.call(
ApiAddons.getLvMenu(
this.event.lehrveranstaltung_id,
studiensemesterKurzbz,
),
);
if (lvMenuResponse.meta.status !== "success") return;
this.lvMenu = lvMenuResponse.data;
},
},
created() {
if (this.event.type == 'lehreinheit') {
this.$api
.call(ApiLvPlan.getLehreinheitStudiensemester(Array.isArray(this.event.lehreinheit_id) ? this.event.lehreinheit_id[0] : this.event.lehreinheit_id))
.then(res => res.data)
.then(studiensemester_kurzbz => this.$api.call(
ApiAddons.getLvMenu(
this.event.lehrveranstaltung_id,
studiensemester_kurzbz
)
))
.then(res => {
this.lvMenu = res.data;
});
async created() {
if (this.$props.event.type == 'lehreinheit') {
await this.getLvMenu();
}
},
template: `
+2 -2
View File
@@ -381,13 +381,13 @@ export default {
<template v-for="lehrveranstaltung in lehrveranstaltungen" :key="lehrveranstaltung.lehrveranstaltung_id">
<div class="card" v-if="Array.isArray(lehrveranstaltung.lehrveranstaltungen) && lehrveranstaltung.lehrveranstaltungen.length >0" >
<div class="card-header">
<h5 class=" card-title">{{isGermanLanguage == 'German' ? lehrveranstaltung.bezeichnung : lehrveranstaltung.bezeichnung_english }}</h5>
<h5 class=" card-title">{{isGermanLanguage ? lehrveranstaltung.bezeichnung : lehrveranstaltung.bezeichnung_english }}</h5>
<h6 class=" card-subtitle">{{lehrveranstaltung.lehrform_kurzbz}}</h6>
</div>
<div class="card-body">
<ul class="list-group list-group-flush">
<li class="d-flex list-group-item" v-for="lv in lehrveranstaltung.lehrveranstaltungen">
<a class="fhc-link-color d-block me-auto" href="#" @click="openLvUebersicht(lv)">{{isGermanLanguage == 'German' ? lv.bezeichnung : lv.bezeichnung_english}}</a>
<a class="fhc-link-color d-block me-auto" href="#" @click="openLvUebersicht(lv)">{{isGermanLanguage ? lv.bezeichnung : lv.bezeichnung_english}}</a>
<p>{{lv.lehrform_kurzbz}}</p>
</li>
</ul>
+49 -20
View File
@@ -62,6 +62,9 @@ export default {
return [type, result];
}));
},
hiddenWidgets() {
return this.widgets.filter(widget => widget.hidden);
}
},
methods: {
@@ -69,6 +72,9 @@ export default {
this.$refs.widgetpicker
.getWidget()
.then(widget_id => {
if (widget_id.hidden)
this.widgetUnhide(widget_id, widget.place);
widget.widget = widget_id;
// NOTE(chris): min size
widget.place = Object.fromEntries(Object.entries(widget.place).map(([key, value]) => {
@@ -131,17 +137,6 @@ export default {
this.widgets.forEach((widget, i) => {
if (failed.includes(widget.id)) {
this.widgets[i] = structuredClone(ObjectUtils.deepToRaw(this.originalWidgets[widget.id]));
/** NOTE(chris): if you wanna hide or unhide a
* preset and it fails: switch around the hidden
* value to revert it properly (checkboxes can't
* really handle it otherwise)
*/
if (payload[widget.id].hidden !== undefined) {
this.widgets[i].hidden = payload[widget.id].hidden;
this.$nextTick(() => {
this.widgets[i] = structuredClone(ObjectUtils.deepToRaw(this.originalWidgets[widget.id]));
});
}
} else if (payload[widget.id]) {
payload[widget.id].id = widget.id;
payload[widget.id].index = widget.index;
@@ -160,6 +155,31 @@ export default {
})
.catch(this.$fhcAlert.handleSystemError);
},
widgetUnhide(orig, place) {
place = Object.fromEntries(Object.entries(place).map(([key, value]) => {
value.w = this.sizeLimits[orig.widget].width.min;
value.h = this.sizeLimits[orig.widget].height.min;
// NOTE(chris): unpin pinned widgets because they got a new placement
value.pinned = false;
return [key, value];
}));
// NOTE(chris): clear placement in other screensizes
Object.keys(orig.place).forEach(key => {
if (!place[key]) {
place[key] = {
x: undefined,
y: undefined,
pinned: false
};
}
});
let payload = {};
payload[orig.id] = {
hidden: false,
place
};
return this.widgetUpdate(payload);
},
async fetchViewData() {
let viewDataResult = await this.$api.call(ApiDashboard.getViewData());
const viewData = viewDataResult.data;
@@ -204,15 +224,23 @@ export default {
},
template: /* html */`
<div class="core-dashboard">
<h3>
{{ userFirstName ? $p.t('global/personalGreeting', [ userFirstName ]) : '' }}
<button
class="btn ms-2"
aria-label="edit dashboard"
v-tooltip="{ showDelay: 1000, value: $p.t('dashboard/edit') }"
@click="editMode = !editMode"
><i class="fa-solid fa-gear" aria-hidden="true"></i></button>
</h3>
<header class="d-flex justify-content-between align-items-baseline">
<h3>
{{ userFirstName ? $p.t('global/personalGreeting', [ userFirstName ]) : '' }}
</h3>
<div class="form-check form-check-reverse form-switch">
<label class="form-check-label" for="switchEditMode">
{{ $p.t('dashboard/edit') }}
</label>
<input
class="form-check-input"
type="checkbox"
role="switch"
id="switchEditMode"
v-model="editMode"
>
</div>
</header>
<dashboard-section
name="general"
:widgets="widgets"
@@ -223,6 +251,7 @@ export default {
<dashboard-widget-picker
ref="widgetpicker"
:widgets="widgetsSetup"
:hidden-widgets="hiddenWidgets"
></dashboard-widget-picker>
</div>`
}
+2 -26
View File
@@ -36,7 +36,6 @@ export default {
"width",
"height",
"custom",
"hidden",
"editMode",
"loading", // widget got added and is waiting for backend to save in db
"item_data",
@@ -115,14 +114,6 @@ export default {
},
ready() {
return this.component && this.arguments !== null;
},
visible: {
get() {
return !this.hidden;
},
set(value) {
this.$emit('remove', this.hidden);
}
}
},
methods: {
@@ -202,10 +193,8 @@ export default {
},
template: /*html*/ `
<article
v-if="!hidden || editMode"
class="dashboard-item card overflow-hidden h-100 position-relative"
:class="{
'hidden-widget': hidden,
[arguments?.className]: arguments && arguments.className
}"
>
@@ -299,9 +288,9 @@ export default {
>
<i class="fa-solid fa-gear" aria-hidden="true"></i>
</a>
<!-- delete button -->
<!-- delete/hide button -->
<a
v-if="custom && editMode"
v-if="editMode"
href="#"
class="col-auto px-1"
:aria-label="$p.t('dashboard/widget_delete')"
@@ -310,19 +299,6 @@ export default {
>
<i class="fa-solid fa-trash" aria-hidden="true"></i>
</a>
<!-- hide button -->
<Transition>
<div v-if="!custom && editMode" class="col-auto px-1 form-switch">
<input
type="checkbox"
role="switch"
v-model="visible"
class="form-check-input ms-0"
:value="true"
:aria-label="$p.t('dashboard/widget_toggle_visibility')"
>
</div>
</Transition>
</header>
<!-- TODO Manu rename/remove-->
<div v-if="ready" class="card-body overflow-hidden p-0">
+3 -16
View File
@@ -124,8 +124,6 @@ export default {
return { ...item, ...placement, weight };
});
if (this.editModeIsActive)
return placedItems;
return placedItems.filter(item => !item.hidden);
}
},
@@ -141,22 +139,12 @@ export default {
handleConfigClosed() {
this.configOpened = false
},
removeWidget(item, revert) {
removeWidget(item) {
if (item.custom) {
BsConfirm.popup(this.$p.t('dashboard', 'alert_deleteWidget')).then(() => this.$emit('widgetRemove', item.id, this.name));
} else {
let update = {};
update[item.id] = { hidden: !revert };
if (!revert) {
// NOTE(chris): move to last line
update[item.id].place = [];
let y = this.gridHeight;
if (this.additionalRow)
y--;
update[item.id].place[this.gridWidth] = { x: 0, y };
}
update[item.id] = { hidden: true };
this.updatePreset(update);
}
},
@@ -251,13 +239,12 @@ export default {
:loading="item.loading"
:config="item.config"
:custom="item.custom"
:hidden="item.hidden"
:editMode="editModeIsActive"
:place="item.place[gridWidth]"
:widget-template="indexedWidgetsTemplates[item.widget]"
:source="adminMode ? null : item.source || 'custom'"
@change="saveConfig($event, item)"
@remove="removeWidget(item, $event)"
@remove="removeWidget(item)"
@config-opened="handleConfigOpened"
@config-closed="handleConfigClosed"
@pin-item="updatePositions"
+44 -11
View File
@@ -7,11 +7,21 @@ export default {
WidgetIcon,
},
props: [
"widgets"
"widgets",
"hiddenWidgets"
],
data: () => ({
callbacks: {}
}),
computed: {
hasAnyWidgets() {
if (!this.widgets)
return false;
if (!this.widgets.length && !this.hiddenWidgets?.length)
return false;
return true;
}
},
methods: {
getWidget() {
return new Promise((resolve,reject) => {
@@ -37,23 +47,46 @@ export default {
<bs-modal
ref="modal"
class="fade"
:dialog-class="{ 'modal-fullscreen-sm-down': 1, 'modal-xl': widgets && widgets.length > 0 }"
:dialog-class="{ 'modal-fullscreen-sm-down': 1, 'modal-xl': hasAnyWidgets }"
@hiddenBsModal="close"
>
<template v-slot:title>{{ $p.t('dashboard/createWidget') }}</template>
<template v-slot:default>
<div v-if="widgets" class="row g-2">
<div v-if="!widgets.length">
{{ $p.t('dashboard/noWidgetsAvailable') }}
</div>
<template v-if="widgets && hiddenWidgets">
<div
v-for="widget in widgets"
:key="widget.widget_id"
class="widget-icon-container col-sm-6 col-md-4 col-lg-3 col-xl-2"
v-if="!widgets.length && !hiddenWidgets.length"
class="row g-2"
>
<widget-icon @select="pick" :widget="widget"></widget-icon>
<div>{{ $p.t('dashboard/noWidgetsAvailable') }}</div>
</div>
</div>
<template v-else>
<div
v-if="hiddenWidgets.length"
class="row g-2"
:class="widgets.length ? 'border-bottom pb-3 mb-3' : ''"
>
<div
v-for="widget in hiddenWidgets"
:key="widget.widget_id"
class="widget-icon-container col-sm-6 col-md-4 col-lg-3 col-xl-2"
>
<widget-icon @select="pick(widget)" :widget="widgets.find(w => w.widget_id == widget.widget)"></widget-icon>
</div>
</div>
<div
v-if="widgets.length"
class="row g-2"
>
<div
v-for="widget in widgets"
:key="widget.widget_id"
class="widget-icon-container col-sm-6 col-md-4 col-lg-3 col-xl-2"
>
<widget-icon @select="pick" :widget="widget"></widget-icon>
</div>
</div>
</template>
</template>
<div v-else class="text-center">
<i class="fa-solid fa-spinner fa-pulse fa-3x"></i>
</div>
+13
View File
@@ -233,6 +233,14 @@ export default {
if (value == oldValue)
return;
/** NOTE(chris): tempPositionUpdates depends on the cols not to be
* updated. So if they do we reset to get a clean value. In case the
* drag and drop operation is still motion on the next tick this
* will be correct again and if there is no such operation we clean
* up.
*/
this.tempPositionUpdates = null;
this.reinitGrid();
},
rows: {
@@ -246,6 +254,11 @@ export default {
},
indexedItems: {
handler(value) {
/** NOTE(chris): there was an update of the items, so in case
* it came from a drag and drop operation we need to clean up
*/
this.tempPositionUpdates = null;
this.reinitGrid();
},
immediate: true,
+1 -1
View File
@@ -40,7 +40,7 @@ export default {
query: Vue.computed(() => this.lastQuery),
};
},
inject: ["isMobile"],
inject: { isMobile: { default: false } },
data: function () {
return {
searchtimer: null,
+1
View File
@@ -100,6 +100,7 @@ require_once('dbupdate_3.4/68530_Dashboard_Cleanup.php');
require_once('dbupdate_3.4/75959_StudVw_Automatische_Tags.php');
require_once('dbupdate_3.4/76160_lvv_favorites.php');
require_once('dbupdate_3.4/77080_tabulator_presets_table.php');
require_once('dbupdate_3.4/78292_gehaltsanpassungtyp.php');
// *** Pruefung und hinzufuegen der neuen Attribute und Tabellen
echo '<H2>Pruefe Tabellen und Attribute!</H2>';
@@ -0,0 +1,47 @@
<?php
if (! defined('DB_NAME')) exit('No direct script access allowed');
if ($result = $db->db_query("SELECT * FROM information_schema.tables WHERE table_name='tbl_gehaltsanpassungtyp' AND table_schema='hr'"))
{
if ($db->db_num_rows($result) == 0)
{
$qry = "
ALTER TABLE
hr.tbl_gehaltsbestandteil
ADD COLUMN IF NOT EXISTS
gehaltsanpassungtyp_kurzbz character varying(32);
COMMENT ON COLUMN hr.tbl_gehaltsbestandteil.gehaltsanpassungtyp_kurzbz IS E'Ersteinstufung, indiv. Erhöhung, etc.';
CREATE TABLE IF NOT EXISTS hr.tbl_gehaltsanpassungtyp
(
gehaltsanpassungtyp_kurzbz character varying(32) NOT NULL,
bezeichnung varchar(256),
sort smallint,
aktiv boolean NOT NULL DEFAULT true,
CONSTRAINT tbl_gehaltsanpassungtyp_pk PRIMARY KEY (gehaltsanpassungtyp_kurzbz)
);
COMMENT ON TABLE hr.tbl_gehaltsanpassungtyp IS E'Key-Table of Salary Adaption Types';
ALTER TABLE hr.tbl_gehaltsbestandteil DROP CONSTRAINT IF EXISTS tbl_gehaltsanpassungtyp_fk;
ALTER TABLE hr.tbl_gehaltsbestandteil ADD CONSTRAINT tbl_gehaltsanpassungtyp_fk FOREIGN KEY (gehaltsanpassungtyp_kurzbz)
REFERENCES hr.tbl_gehaltsanpassungtyp (gehaltsanpassungtyp_kurzbz) MATCH FULL
ON DELETE RESTRICT ON UPDATE CASCADE;
GRANT SELECT, UPDATE, INSERT, DELETE ON hr.tbl_gehaltsanpassungtyp TO vilesci;
INSERT INTO hr.tbl_gehaltsanpassungtyp(gehaltsanpassungtyp_kurzbz, bezeichnung, sort, aktiv) VALUES('ersteinstufung','Ersteinstufung', 1, true);
INSERT INTO hr.tbl_gehaltsanpassungtyp(gehaltsanpassungtyp_kurzbz, bezeichnung, sort, aktiv) VALUES('individuelle_erhoehung','Indiv. Gehaltserhöhung', 2, true);
INSERT INTO hr.tbl_gehaltsanpassungtyp(gehaltsanpassungtyp_kurzbz, bezeichnung, sort, aktiv) VALUES('strukturelle_anpassung','Strukturelle Anpassung', 3, true);
INSERT INTO hr.tbl_gehaltsanpassungtyp(gehaltsanpassungtyp_kurzbz, bezeichnung, sort, aktiv) VALUES('funktionsaenderung','Funktionsänderung', 4, true);
";
if (! $db->db_query($qry))
echo '<strong>Gehaltsanpassungtyp: ' . $db->db_last_error() . '</strong><br>';
else
echo 'Tabelle und Feld für Gehaltsanpassungtyp wurden neu erstellt<br>';
}
}
+60
View File
@@ -29742,6 +29742,46 @@ array(
)
)
),
array(
'app' => 'core',
'category' => 'profil',
'phrase' => 'name_title_clipboard_copy_confirmation',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Name mit Titel in die Zwischenablage kopiert.',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Full name with title copied to clipboard.',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'profil',
'phrase' => 'name_title_copy',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Name mit Titel kopieren',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Copy full name with title',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'profil',
@@ -60852,6 +60892,26 @@ I have been informed that I am under no obligation to consent to the transmissio
)
)
),
array(
'app' => 'core',
'category' => 'LvPlan',
'phrase' => 'copy',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Kopieren',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Copy',
'description' => '',
'insertvon' => 'system'
)
)
),
// ### LvPlanStgOrg END ###
// DMS-Link Phrasen Start
array(