add tagsAssignmentModal extraction from tempus component

This commit is contained in:
Ivymaster
2026-07-22 17:01:11 +02:00
parent 4e58705107
commit 4e21bb37d3
2 changed files with 199 additions and 204 deletions
@@ -0,0 +1,193 @@
/**
* 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,
};
},
computed: {
dropdownParsedAvailableTags() {
return this.availableTags.map((tag) => ({
label: tag.bezeichnung,
value: tag.tag_typ_kurzbz,
data: tag,
}));
},
},
methods: {
async open(calendar) {
if (!calendar?.kalender_id) return;
this.calendar = calendar;
this.availableTags = await this.fetchAvailableTags();
this.filteredAvailableTags = [...this.dropdownParsedAvailableTags];
this.assignedTags = await this.fetchAssignedTagsByCalender(
calendar.eindeutige_gruppen_id,
);
this.show();
},
show() {
this.$refs.modal.show();
},
hide() {
this.$refs.modal.hide();
},
reset() {
this.calendar = null;
this.availableTags = [];
this.filteredAvailableTags = [];
this.selectedAvailableTag = null;
this.assignedTags = [];
},
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.$refs.tagComponent?.openModal(event.value.data);
},
editTag(tag) {
this.$refs.tagComponent?.editTag(tag.notiz_id);
},
async handleCalendarTagChange() {
const calendarGroupId = this.calendar?.eindeutige_gruppen_id;
if (!calendarGroupId) return;
this.assignedTags =
await this.fetchAssignedTagsByCalender(calendarGroupId);
this.$emit("tagsChanged");
},
},
template: /* html */`
<bs-modal
ref="modal"
@hide-bs-modal="reset"
body-class="p-4"
class="bootstrap-prompt"
data-cy="tagsAssignmentModal"
>
<template #title>{{ $p.t('ui', 'tags_assignment_modal_title') }}</template>
<template #default>
<div class="mb-5">
<form-input
v-if="availableTags.length"
@item-select="selectTag"
:label="$capitalize($p.t('ui', 'tags'))"
:suggestions="filteredAvailableTags"
:option-value="option => option.value"
:option-label="option => option.label"
@complete="filterAvailableTags"
dropdown
force-selection
type="autocomplete"
name="availableTags"
:close-on-select="false"
>
<template #option="{ option }">
<span :class="['tag', option.data.style]">{{ option.label }}</span>
</template>
</form-input>
</div>
<div>
<div class="d-flex align-items-center justify-content-between mb-2">
<h6 class="mb-1 mx-auto text-bold fw-1">
{{ $p.t('ui', 'assigned_tags_subtitle') }}
</h6>
</div>
<div v-if="assignedTags.length">
<span
v-for="tag in assignedTags"
:key="tag.tag_typ_kurzbz"
:class="[tag.style, { tag_done: tag.done }]"
@click="editTag(tag)"
class="tag"
>{{ tag.bezeichnung }}</span>
</div>
<div v-else class="d-flex align-items-center justify-content-center mb-2">
<p class="text-muted mb-0">{{ $p.t('ui', 'no_assigned_tags') }}</p>
</div>
</div>
</template>
</bs-modal>
<core-tag
v-if="calendar?.eindeutige_gruppen_id"
ref="tagComponent"
:is-list-item-shown="false"
:endpoint="tagEndpoint"
:values="[calendar.eindeutige_gruppen_id]"
zuordnung_typ="eindeutige_kalender_gruppen_id"
@added="handleCalendarTagChange"
@deleted="handleCalendarTagChange"
@updated="handleCalendarTagChange"
/>
`,
};
+6 -204
View File
@@ -28,7 +28,6 @@ import ApiSearchbar from "../../api/factory/searchbar.js";
import ApiRenderers from "../../api/factory/renderers.js";
import ApiTempusConfig from "../../api/factory/tempus/config.js";
import ApiBetriebsmittel from "../../api/factory/betriebsmittel.js";
import ApiTempusTag from "../../api/factory/tempus/tag.js";
import AppMenu from "../AppMenu.js";
import drop from "../../directives/drop.js";
import AppConfig from "../AppConfig.js";
@@ -39,7 +38,6 @@ import StvVerband from "../Stv/Studentenverwaltung/Verband.js";
import ApiStudiengangTree from "../../api/factory/tempus/studiengangtree.js";
import ApiInfo from "../../api/factory/tempus/info.js";
import StvStudiensemester from "../Stv/Studentenverwaltung/Studiensemester.js";
import FormInput from "../../../js/components/Form/Input.js";
import Reservierung from "./Reservierung.js";
import { getTempusShortcuts } from "./shortcuts.js";
import KeyboardShortcuts from "./KeyboardShortcuts.js";
@@ -47,8 +45,8 @@ import { useContextMenuActions } from "../../composables/Tempus/ContextMenuActio
import MultiWeekPlanModal from "./MultiWeekPlanModal.js";
import HistoryModal from "./HistoryModal.js";
import ResourcesAssignmentModal from "./ResourcesAssignmentModal.js";
import TagsAssignmentModal from "./TagsAssignmentModal.js";
import { getTempusSearchbarOptions } from "./Filters/searchbarOptions.js";
import CoreTag from '../Tag/Tag.js';
export default {
name: "Tempus",
@@ -68,13 +66,12 @@ export default {
StvVerband,
StvStudiensemester,
Multiselect: primevue.multiselect,
FormInput,
Reservierung,
KeyboardShortcuts,
MultiWeekPlanModal,
HistoryModal,
ResourcesAssignmentModal,
CoreTag,
TagsAssignmentModal,
},
props: {
defaultSemester: String,
@@ -101,7 +98,7 @@ export default {
openRaumauswahl: (orig) => this.openRaumauswahl(orig),
openResourcesAssignmentModal: (orig) =>
this.$refs.resourcesAssignmentModal?.open(orig),
openTagsModal: (orig) => this.openTagsAssignmentModal(orig),
openTagsModal: (orig) => this.$refs.tagsAssignmentModal?.open(orig),
openHistory: (orig) => this.openHistory(orig),
deleteEntry: (orig) => this.deleteEntry(orig),
syncToLecturer: (orig) => this.syncToLecturer(orig),
@@ -170,15 +167,6 @@ export default {
endTime: null,
},
studiengaenge_all: [],
tagsAssignmentModal: {
calendar: null,
availableTags: [],
filteredAvailableTags: [],
selectedAvailableTag: null,
assignedTags: [],
areFormButtonsDisplayed: false,
},
tagEndpoint: ApiTempusTag,
};
},
computed: {
@@ -213,16 +201,6 @@ export default {
searchbaroptions() {
return getTempusSearchbarOptions(this);
},
dropdownParsedAvailableTags() {
return this.tagsAssignmentModal.availableTags
.map((tag) => {
return {
label: tag.bezeichnung,
value: tag.tag_typ_kurzbz,
data: tag,
};
});
},
},
methods: {
async openRaumauswahl(orig) {
@@ -236,19 +214,6 @@ export default {
this.$refs.raumModal.show();
});
},
async openTagsAssignmentModal(orig) {
console.log("openTagsAssignmentModal called with orig:", orig);
if (!orig?.kalender_id) return;
this.tagsAssignmentModal.calendar = orig;
this.tagsAssignmentModal.availableTags = await this.fetchAvailableTags();
this.tagsAssignmentModal.filteredAvailableTags = [
...this.dropdownParsedAvailableTags,
];
this.tagsAssignmentModal.assignedTags = await this.fetchAssignedTagsByCalender(orig.eindeutige_gruppen_id);
this.$refs.tagsAssignmentModal.show();
},
async deleteEntry(orig) {
if (!orig?.kalender_id) return;
@@ -794,114 +759,6 @@ export default {
this.extraBackgrounds = res;
},
async fetchAvailableTags() {
let getAvailableTags = await this.$api.call(
ApiTempusTag.getTags(),
);
if (getAvailableTags.meta.status === "success") {
return getAvailableTags.data;
} else {
this.$fhcAlert.alertError(
this.$p.t("ui", "failed_available_tags_fetch_error_message"),
);
}
return [];
},
async fetchAssignedTagsByCalender(calenderId) {
let getAssignedTags = await this.$api.call(
ApiTempusTag.getTagsByCalendar(calenderId),
);
if (getAssignedTags.meta.status === "success") {
return getAssignedTags.data.filter((unit) => !!unit);
} else {
this.$fhcAlert.alertError(
this.$p.t("ui", "failed_assigned_tags_fetch_error_message"),
);
}
return [];
},
filterAvailableTags(event) {
this.tagsAssignmentModal.filteredAvailableTags;
const query = event.query.toLowerCase();
if (!query) {
return (this.tagsAssignmentModal.filteredAvailableTags = [
...this.dropdownParsedAvailableTags.filter((tag) => {
return !this.tagsAssignmentModal.assignedTags.some(
(assigned) => assigned.tag_id === tag.value,
);
}),
]);
}
return (this.tagsAssignmentModal.filteredAvailableTags =
this.dropdownParsedAvailableTags
.filter((tag) => {
return !this.tagsAssignmentModal.assignedTags.some(
(assigned) => assigned.tag_id === tag.value,
);
})
.filter((tag) => {
return tag.label.toLowerCase().includes(query);
}));
},
removeAssignedTag(tag) {
this.tagsAssignmentModal.assignedTags =
this.tagsAssignmentModal.assignedTags.filter(
(assigned) => assigned.tag_id !== tag.tag_id,
);
this.tagsAssignmentModal.areFormButtonsDisplayed = true;
},
async refreshTagsAssignmentModalData(calenderItem) {
this.tagsAssignmentModal.availableTags =
await this.fetchAvailableTags();
this.tagsAssignmentModal.filteredAvailableTags = [
...this.dropdownParsedAvailableTags,
];
this.tagsAssignmentModal.assignedTags =
await this.fetchAssignedTagsByCalender(calenderItem.eindeutige_gruppen_id);
this.tagsAssignmentModal.selectedAvailableTag = null;
this.tagsAssignmentModal.areFormButtonsDisplayed = false;
},
async saveAssignedTagsToCalendarItem(calenderItem, assignedTags) {
let saveAssignedTagsByCalender = await this.$api.call(
ApiTempusTag.getTags(
calenderItem.eindeutige_gruppen_id,
assignedTags,
),
);
if (saveAssignedTagsByCalender.meta.status === "success") {
this.$fhcAlert.alertSuccess(
this.$p.t("ui", "assigned_tags_save_success_message"),
);
await this.refreshTagsAssignmentModalData(calenderItem);
} else {
this.$fhcAlert.alertError(
this.$p.t("ui", "failed_assigned_tags_save_error_message"),
);
}
this.$refs.calendar.resetEventLoader();
this.$refs.tagsAssignmentModal.hide();
},
closeTagsAssignmentModal() {
this.tagsAssignmentModal = {
availableTags: [],
filteredAvailableTags: [],
selectedAvailableTag: null,
assignedTags: [],
areFormButtonsDisplayed: false,
};
},
async handleCalenderTagChange(calenderID) {
this.tagsAssignmentModal.assignedTags =
await this.fetchAssignedTagsByCalender(calenderID);
this.$refs.calendar.resetEventLoader();
},
removeLecturer(uid) {
if (uid == null) {
for (const lect of this.lecturers) delete this.overlayCache[lect.uid];
@@ -1283,66 +1140,11 @@ export default {
ref="resourcesAssignmentModal"
@save-finished="$refs.calendar.resetEventLoader()"
/>
<bs-modal
<tags-assignment-modal
ref="tagsAssignmentModal"
@hideBsModal="closeTagsAssignmentModal"
bodyClass="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="tagsAssignmentModal.availableTags.length"
@itemSelect="(option) => { tagsAssignmentModal.selectedAvailableTag = option.value; $refs.tagComponent.openModal(option.value.data); }"
:label="$capitalize($p.t('ui', 'tags'))"
:suggestions="tagsAssignmentModal.filteredAvailableTags"
:optionValue="(option) => option.value"
:optionLabel="(option) => option.label"
@complete="filterAvailableTags"
dropdown
forceSelection
type="autocomplete"
name="availableTags"
:closeOnSelect="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="tagsAssignmentModal.assignedTags.length">
<span
v-for="tag in tagsAssignmentModal.assignedTags"
:key="tag.tag_typ_kurzbz"
:class="[tag.style, { tag_done: tag.done }]"
@click="$refs.tagComponent.editTag(tag.notiz_id)"
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>
@tags-changed="$refs.calendar.resetEventLoader()"
/>
<history-modal ref="historyModal" :entries="historyEntries" />
<core-tag
v-if="tagEndpoint && tagsAssignmentModal.calendar?.eindeutige_gruppen_id"
ref="tagComponent"
:isListItemShown="false"
:endpoint="tagEndpoint"
:values="[tagsAssignmentModal.calendar?.eindeutige_gruppen_id]"
:zuordnung_typ="'eindeutige_kalender_gruppen_id'"
@added="handleCalenderTagChange(tagsAssignmentModal.calendar?.eindeutige_gruppen_id)"
@deleted="handleCalenderTagChange(tagsAssignmentModal.calendar?.eindeutige_gruppen_id)"
@updated="handleCalenderTagChange(tagsAssignmentModal.calendar?.eindeutige_gruppen_id)"
></core-tag>
<reservierung
ref="reservierung"
:rooms="rooms"