THANASOFT-HFC/calendar/src/views/EditSimple.vue
2025-04-10 14:02:22 +03:00

614 lines
18 KiB
Vue

<!--
- @copyright Copyright (c) 2019 Georg Ehrke <oc.list@georgehrke.com>
-
- @author Georg Ehrke <oc.list@georgehrke.com>
- @author Richard Steinmetz <richard@steinmetz.cloud>
-
- @license AGPL-3.0-or-later
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero 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 Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<Popover
ref="popover"
:shown="showPopover"
:auto-hide="false"
:placement="placement"
:boundary="boundaryElement"
popover-base-class="event-popover"
:triggers="[]"
>
<div class="event-popover__inner">
<template v-if="isLoading && !isSaving">
<PopoverLoadingIndicator />
</template>
<template v-else-if="isError">
<div class="event-popover__top-right-actions">
<Actions>
<ActionButton @click="cancel">
<template #icon>
<Close :size="20" decorative />
</template>
{{ $t("calendar", "Close") }}
</ActionButton>
</Actions>
</div>
<EmptyContent
:name="$t('calendar', 'Event does not exist')"
:description="error"
>
<template #icon>
<CalendarBlank :size="20" decorative />
</template>
</EmptyContent>
</template>
<template v-else>
<div class="event-popover__top-right-actions">
<Actions v-if="!isLoading && !isError && !isNew" :force-menu="true">
<ActionLink
v-if="!hideEventExport && hasDownloadURL"
:href="downloadURL"
>
<template #icon>
<Download :size="20" decorative />
</template>
{{ $t("calendar", "Export") }}
</ActionLink>
<ActionButton
v-if="!canCreateRecurrenceException && !isReadOnly"
@click="duplicateEvent()"
>
<template #icon>
<ContentDuplicate :size="20" decorative />
</template>
{{ $t("calendar", "Duplicate") }}
</ActionButton>
<ActionButton
v-if="canDelete && !canCreateRecurrenceException"
@click="deleteAndLeave(false)"
>
<template #icon>
<Delete :size="20" decorative />
</template>
{{ $t("calendar", "Delete") }}
</ActionButton>
<ActionButton
v-if="canDelete && canCreateRecurrenceException"
@click="deleteAndLeave(false)"
>
<template #icon>
<Delete :size="20" decorative />
</template>
{{ $t("calendar", "Delete this occurrence") }}
</ActionButton>
<ActionButton
v-if="canDelete && canCreateRecurrenceException"
@click="deleteAndLeave(true)"
>
<template #icon>
<Delete :size="20" decorative />
</template>
{{ $t("calendar", "Delete this and all future") }}
</ActionButton>
</Actions>
<Actions>
<ActionButton @click="cancel">
<template #icon>
<Close :size="20" decorative />
</template>
{{ $t("calendar", "Close") }}
</ActionButton>
</Actions>
</div>
<CalendarPickerHeader
:value="selectedCalendar"
:calendars="calendars"
:is-read-only="isReadOnlyOrViewing || !canModifyCalendar"
@update:value="changeCalendar"
/>
<PropertyTitle
:value="titleOrPlaceholder"
:is-read-only="isReadOnlyOrViewing"
@update:value="updateTitle"
/>
<PropertyTitleTimePicker
:start-date="startDate"
:start-timezone="startTimezone"
:end-date="endDate"
:end-timezone="endTimezone"
:is-all-day="isAllDay"
:is-read-only="isReadOnlyOrViewing"
:can-modify-all-day="canModifyAllDay"
:user-timezone="currentUserTimezone"
@update-start-date="updateStartDate"
@update-start-timezone="updateStartTimezone"
@update-end-date="updateEndDate"
@update-end-timezone="updateEndTimezone"
@toggle-all-day="toggleAllDay"
/>
<div style='display:flex ;margin-left: 7%;'>
<div style='width:70%'>
<PropertySelectAbsenceType
:value="absenceType"
:is-read-only="isReadOnly"
:prop-model="rfcProps.absenceType"
:noWrap='true'
sle
@update:value="updateAbsenceType" />
</div>
<!-- <div style='width:30% ;margin-top: -11px;'>
<PropertyIsPrivate
:is-read-only="isReadOnly"
:is-private="isPrivate"
@toggle-is-private="toggleIsPrivate"/>
</div> -->
</div>
<!-- <PropertyIsLeave
:is-read-only="isReadOnlyOrViewing"
:is-leave="isLeave"
@toggle-is-leave="toggleIsLeave"
/> -->
<!-- <PropertyIsCalendarPending
:is-read-only="isReadOnlyOrViewing"
:is-calendar-pending="isCalendarPending"
@toggle-is-calendar-pending="toggleIsCalendarPending" /> -->
<PropertySelectClient
class="property-location"
url="/apps/gestion/ajaxGetClientsName"
:is-read-only="isReadOnly"
:prop-model="rfcProps.clients"
:value="client"
:linkify-links="true"
@update:value="updateClient"
/>
<PropertySelectLieu
class="property-location"
url="/apps/gestion/ajaxGetLieux"
:is-read-only="isReadOnly"
:prop-model="rfcProps.locations"
:value="location"
:linkify-links="true"
@update:value="updateLocation"
/>
<PropertySelectArticle
:is-read-only="isReadOnlyOrViewing"
url="/apps/gestion/ajaxGetProduits?orderDirection=ASC"
:prop-model="rfcProps.articles"
:value="description"
:linkify-links="true"
@add-single-value="addArticle"
@remove-single-value="removeArticle"
/>
<PropertyText
:is-read-only="isReadOnly"
:prop-model="rfcProps.comment"
:value="comment"
:linkify-links="false"
@update:value="updateComment"
/>
<InviteesList
class="event-popover__invitees"
:hide-if-empty="true"
:hide-buttons="true"
:hide-errors="true"
:show-header="true"
:is-read-only="isReadOnlyOrViewing"
:is-shared-with-me="isSharedWithMe"
:calendar-object-instance="calendarObjectInstance"
:limit="3"
/>
<InvitationResponseButtons
v-if="isViewedByAttendee && isViewing"
class="event-popover__response-buttons"
:attendee="userAsAttendee"
:calendar-id="calendarId"
@close="closeEditorAndSkipAction"
/>
<SaveButtons
v-if="!isWidget"
class="event-popover__buttons"
:can-create-recurrence-exception="canCreateRecurrenceException"
:is-new="isNew"
:is-read-only="isReadOnlyOrViewing"
:force-this-and-all-future="forceThisAndAllFuture"
:show-more-button="true"
:more-button-type="isViewing ? 'tertiary' : undefined"
:grow-horizontally="!isViewing && canCreateRecurrenceException"
:disabled="isSaving"
:is-calendar-pending="isCalendarPending"
@save-this-only="saveAndView(false)"
@save-this-and-all-future="saveAndView(true)"
@show-more="showMore"
@save-pending-calendar-event="saveAsPendingCalendarEvent"
>
<NcButton
@click="viewDefunt"
v-if="defuntUrl"
:type="undefined"
:class="'d-flex w-max-content'"
>
Voir le defunt
</NcButton>
<NcButton
v-if="!isReadOnly && isViewing"
:type="isViewedByAttendee ? 'tertiary' : undefined"
@click="isViewing = false"
>
<template #icon>
<EditIcon :size="20" />
</template>
{{ $t("calendar", "Edit") }}
</NcButton>
</SaveButtons>
</template>
</div>
</Popover>
</template>
<script>
import {
NcActions as Actions,
NcActionButton as ActionButton,
NcActionLink as ActionLink,
NcEmptyContent as EmptyContent,
NcPopover as Popover,
NcButton,
} from "@nextcloud/vue";
import axios from "axios";
import { generateUrl } from "@nextcloud/router";
import EditorMixin from "../mixins/EditorMixin.js";
import PropertyTitle from "../components/Editor/Properties/PropertyTitle.vue";
import PropertyTitleTimePicker from "../components/Editor/Properties/PropertyTitleTimePicker.vue";
import PropertyText from "../components/Editor/Properties/PropertyText.vue";
import SaveButtons from "../components/Editor/SaveButtons.vue";
import PopoverLoadingIndicator from "../components/Popover/PopoverLoadingIndicator.vue";
import { getPrefixedRoute } from "../utils/router.js";
import InvitationResponseButtons from "../components/Editor/InvitationResponseButtons.vue";
import CalendarPickerHeader from "../components/Editor/CalendarPickerHeader.vue";
import InviteesList from "../components/Editor/Invitees/InviteesList.vue";
import CalendarBlank from "vue-material-design-icons/CalendarBlank.vue";
import Close from "vue-material-design-icons/Close.vue";
import Delete from "vue-material-design-icons/Delete.vue";
import Download from "vue-material-design-icons/Download.vue";
import ContentDuplicate from "vue-material-design-icons/ContentDuplicate.vue";
import EditIcon from "vue-material-design-icons/Pencil.vue";
import { mapState } from "vuex";
import PropertySelect from "../components/Editor/Properties/PropertySelect.vue";
import PropertySelectAjax from "../components/Editor/Properties/PropertySelectAjax.vue";
import PropertySelectAjaxMultiple from "../components/Editor/Properties/PropertySelectAjaxMultiple.vue";
import PropertySelectLieu from "../components/Editor/Properties/PropertySelectLieu.vue";
import PropertySelectClient from "../components/Editor/Properties/PropertySelectClient.vue";
import PropertySelectArticle from "../components/Editor/Properties/PropertySelectArticle.vue";
import PropertyIsLeave from "../components/Editor/Properties/PropertyIsLeave.vue";
import PropertyIsCalendarPending from "../components/Editor/Properties/PropertyIsCalendarPending";
import PropertySelectAbsenceType from "../components/Editor/Properties/PropertySelectAbsenceType.vue";
import PropertyIsPrivate from "../components/Editor/Properties/PropertyIsPrivate.vue";
export default {
name: "EditSimple",
components: {
PropertyIsLeave,
PropertyIsCalendarPending,
PropertySelectAjaxMultiple,
PropertySelectAjax,
PropertySelectLieu,
PropertySelectClient,
PropertySelectArticle,
PropertySelect,
PopoverLoadingIndicator,
SaveButtons,
PropertyText,
PropertyTitleTimePicker,
PropertyTitle,
PropertySelectAbsenceType,
Popover,
Actions,
ActionButton,
ActionLink,
EmptyContent,
CalendarBlank,
Close,
Download,
ContentDuplicate,
Delete,
InvitationResponseButtons,
CalendarPickerHeader,
InviteesList,
NcButton,
EditIcon,
PropertyIsPrivate,
},
mixins: [EditorMixin],
data() {
return {
placement: "auto",
hasLocation: false,
hasDescription: false,
isCalendarPending: false,
boundaryElement: null,
isVisible: true,
isViewing: true,
defuntUrl: undefined,
};
},
computed: {
...mapState({
hideEventExport: (state) => state.settings.hideEventExport,
widgetEventDetailsOpen: (state) => state.calendars.widgetEventDetailsOpen,
widgetEventDetails: (state) => state.calendars.widgetEventDetails,
widgetRef: (state) => state.calendars.widgetRef,
}),
showPopover() {
return this.isVisible || this.widgetEventDetailsOpen;
},
/**
* Returns true if the current event is read only or the user is viewing the event
*
* @return {boolean}
*/
isReadOnlyOrViewing() {
return this.isReadOnly || this.isViewing || this.isWidget;
},
/**
* Return the event's title or a placeholder if it is empty
*
* @return {string}
*/
titleOrPlaceholder() {
if (this.title === "" && this.isReadOnlyOrViewing && !this.isLoading) {
return t("calendar", "Untitled event");
}
return this.title;
},
},
watch: {
$route(to, from) {
this.repositionPopover();
// Hide popover when changing the view until the user selects a slot again
this.isVisible = to?.params.view === from?.params.view;
},
calendarObjectInstance() {
this.hasLocation = false;
this.hasDescription = false;
if (
typeof this.calendarObjectInstance.location === "string" &&
this.calendarObjectInstance.location.trim() !== ""
) {
this.hasLocation = true;
}
if (
typeof this.calendarObjectInstance.description === "string" &&
this.calendarObjectInstance.description.trim() !== ""
) {
this.hasDescription = true;
}
if (this.calendarObjectInstance) {
this.getDefuntUrl();
this.isCalendarPending = this.calendarObjectInstance.isCalendarPending ?? false;
}
},
isNew: {
immediate: true,
handler(isNew) {
// New events should be editable from the start
this.isViewing = !isNew;
},
},
},
async mounted() {
if (this.isWidget) {
const objectId = this.widgetEventDetails.object;
const recurrenceId = this.widgetEventDetails.recurrenceId;
await this.$store.dispatch(
"getCalendarObjectInstanceByObjectIdAndRecurrenceId",
{ objectId, recurrenceId }
);
this.calendarId = this.calendarObject.calendarId;
this.isLoading = false;
}
this.boundaryElement = this.isWidget
? document.querySelector(".fc")
: document.querySelector("#app-content-vue > .fc");
window.addEventListener("keydown", this.keyboardCloseEditor);
window.addEventListener("keydown", this.keyboardSaveEvent);
window.addEventListener("keydown", this.keyboardDeleteEvent);
window.addEventListener("keydown", this.keyboardDuplicateEvent);
this.$nextTick(() => {
this.repositionPopover();
});
},
beforeDestroy() {
window.removeEventListener("keydown", this.keyboardCloseEditor);
window.removeEventListener("keydown", this.keyboardSaveEvent);
window.removeEventListener("keydown", this.keyboardDeleteEvent);
window.removeEventListener("keydown", this.keyboardDuplicateEvent);
},
methods: {
showMore() {
// Do not save yet
this.requiresActionOnRouteLeave = false;
const params = Object.assign({}, this.$route.params);
if (this.isNew) {
this.$router.push({ name: "NewSidebarView", params });
} else {
this.$router.push({
name: getPrefixedRoute(this.$route.name, "EditSidebarView"),
params,
});
}
},
getDomElementForPopover(isNew, route) {
let matchingDomObject;
if (this.isWidget) {
const objectId = this.widgetEventDetails.object;
const recurrenceId = this.widgetEventDetails.recurrenceId;
matchingDomObject = this.widgetRef.querySelector(
`.fc-event[data-object-id="${objectId}"][data-recurrence-id="${recurrenceId}"]`
);
this.placement = "auto";
} else if (isNew) {
matchingDomObject = document.querySelector(".fc-highlight");
this.placement = "auto";
if (!matchingDomObject) {
matchingDomObject = document.querySelector(
'.fc-event[data-is-new="yes"]'
);
}
} else {
const objectId = route.params.object;
const recurrenceId = route.params.recurrenceId;
matchingDomObject = document.querySelector(
`.fc-event[data-object-id="${objectId}"][data-recurrence-id="${recurrenceId}"]`
);
this.placement = "auto";
}
if (!matchingDomObject) {
matchingDomObject = document.querySelector("#app-navigation-vue");
this.placement = "right";
}
if (!matchingDomObject) {
matchingDomObject = document.querySelector("body");
this.placement = "auto";
}
console.info(
"getDomElementForPopover",
matchingDomObject,
this.placement
);
return matchingDomObject;
},
repositionPopover() {
const isNew = this.isWidget
? false
: this.$route.name === "NewPopoverView";
this.$refs.popover.$children[0].$refs.reference =
this.getDomElementForPopover(isNew, this.$route);
this.$refs.popover.$children[0].$refs.popper.dispose();
this.$refs.popover.$children[0].$refs.popper.init();
},
/**
* Save changes and leave when creating a new event or return to viewing mode when editing
* an existing event. Stay in editing mode if an error occurrs.
*
* @param {boolean} thisAndAllFuture Modify this and all future events
* @return {Promise<void>}
*/
async saveAndView(thisAndAllFuture) {
// Transitioning from new to edit routes is not implemented for now
if (this.isNew) {
await this.saveAndLeave(thisAndAllFuture);
return;
}
this.isViewing = true;
try {
await this.save(thisAndAllFuture);
this.requiresActionOnRouteLeave = false;
} catch (error) {
this.isViewing = false;
}
},
async saveAsPendingCalendarEvent() {
if (this.isNew) {
await this.savePendingCalendar(false);
return;
}
this.isViewing = true;
try {
await this.savePendingCalendar(false);
this.requiresActionOnRouteLeave = false;
} catch (error) {
this.isViewing = false;
}
},
addArticle(article) {
if (this.description && this.description !== "") {
this.updateDescription(this.description + ";" + article);
} else {
this.updateDescription(article);
}
},
removeArticle(article) {
if (this.description && this.description !== "") {
let values = [];
let items = this.description.split(";");
items.forEach((item) => {
if (item !== article) {
values.push(item);
}
});
this.updateDescription(values.join(";"));
} else {
this.updateDescription(null);
}
},
getDefuntUrl() {
const url = generateUrl(
`/apps/gestion/api/getDefundIdByCalendarUuid/${this.calendarObjectInstance.eventComponent.uid}`
);
axios.get(url).then((response) => {
console.log(this.calendarObjectInstance);
if (response.data.id) {
this.defuntUrl = generateUrl(
`/apps/gestion/defunt/${response.data.id}/show`
);
}
});
},
viewDefunt() {
window.open(this.defuntUrl, "_blank");
},
},
};
</script>