Files
sogo/UI/Templates/SchedulerUI/UIxAppointmentEditorTemplate.wox
T
Leon Klingele f04c73d495 fix(calendar(js)): prevent duplicate event creation in case of an event conflict
In case of an calendar event conflict the form submit button was not
disabled after use, allowing it to be clicked again.
This could have resulted in a double creation of the submit event.
2023-05-10 17:13:23 +02:00

423 lines
21 KiB
XML

<?xml version='1.0' standalone='yes'?>
<container
xmlns="http://www.w3.org/1999/xhtml"
xmlns:var="http://www.skyrix.com/od/binding"
xmlns:const="http://www.skyrix.com/od/constant"
xmlns:label="OGo:label">
<md-dialog
flex="{{ editor.isFullscreen? 100 : 60 }}"
flex-sm="{{ editor.isFullscreen? 100 : 80 }}">
<form name="eventForm" class="md-inline-form">
<md-toolbar>
<div class="md-toolbar-tools sg-no-transition">
<md-icon class="material-icons sg-icon-toolbar-bg">event</md-icon>
<!-- summary -->
<md-button ng-click="editor.toggleFullscreen($event)"
class="md-icon-button hide show-gt-xs"
aria-hidden="true"
ng-if="!isPopup">
<md-tooltip ng-if="centerIsClose" md-direction="bottom">{{ ::'Reduce' | loc }}</md-tooltip>
<md-tooltip ng-else="centerIsClose" md-direction="bottom">{{ ::'Expand' | loc }}</md-tooltip>
<md-icon>{{ editor.isFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</md-icon>
</md-button>
<md-icon ng-if="editor.component.classification == 'confidential'">visibility_off</md-icon>
<md-icon ng-if="editor.component.classification == 'private'">vpn_key</md-icon>
<md-input-container class="md-flex">
<label><var:string label:value="Title"/></label>
<input type="text"
class="md-title"
md-autofocus="true"
ng-model="editor.component.summary"
required="required"/>
</md-input-container>
</div>
</md-toolbar>
<md-dialog-content class="sg-has-form-sections">
<div class="sg-form-section">
<div layout="row" layout-xs="column">
<!-- location -->
<md-input-container flex="50" flex-xs="100">
<label><var:string label:value="Location"/></label>
<input type="text" ng-model="editor.component.location"/>
</md-input-container>
<!-- calendar -->
<md-input-container flex="50" flex-xs="100">
<label><var:string label:value="Calendar"/></label>
<md-select ng-model="editor.component.destinationCalendar"
ng-change="editor.changeCalendar()">
<md-option ng-repeat="calendar in editor.destinationCalendars()"
ng-value="calendar.id">
<div layout="row" layout-align="start center">
<div class="sg-color-chip"
ng-class="calendar.getClassName('bg')"><!-- color --></div>
{{calendar.name}}
</div>
</md-option>
</md-select>
</md-input-container>
</div>
<!-- comment -->
<md-input-container class="md-block md-flex">
<label><var:string label:value="Description"/></label>
<textarea ng-model="editor.component.comment"><!-- comment --></textarea>
</md-input-container>
<!-- categories -->
<md-chips ng-model="editor.component.categories">
<md-chip-template>
<span class="sg-chip-color">
<span ng-style="{ 'background-color': editor.component.constructor.$categories[$chip] }"><!-- color --></span>
</span>
<span>{{$chip}}</span>
</md-chip-template>
<md-autocomplete
md-selected-item="editor.categories.selected"
md-search-text="editor.categories.searchText"
md-items="category in editor.component.constructor.filterCategories(editor.categories.searchText) | orderBy"
md-min-length="0"
label:placeholder="Add a category">
<div layout="row" layout-align="start center">
<div class="sg-color-chip"
ng-style="{ 'background-color': editor.component.constructor.$categories[category] }"><!-- color --></div>
<div md-highlight-text="editor.categories.searchText" md-highlight-flags="^i">{{category}}</div>
</div>
</md-autocomplete>
</md-chips>
<div layout="row" layout-align="space-between center" layout-wrap="layout-wrap">
<!-- classification -->
<md-radio-group class="sg-padded--top"
layout="row"
ng-model="editor.component.classification">
<md-radio-button class="sg-padded--right" value="public">
<var:string label:value="label_Public"/>
</md-radio-button>
<md-radio-button class="sg-padded--right" value="confidential">
<var:string label:value="label_Confidential"/>
</md-radio-button>
<md-radio-button value="private">
<var:string label:value="label_Private"/>
</md-radio-button>
</md-radio-group>
<!-- priority -->
<div class="pseudo-input-container" flex="50" flex-xs="100">
<label class="pseudo-input-label"><var:string label:value="Priority"/></label>
<md-slider-container>
<md-slider class="md-flex"
md-discrete="true"
ng-model="editor.component.priority"
step="1"
min="0"
max="9"
label:aria-label="Priority"><!-- slider --></md-slider>
<span>{{editor.priorityLevel()}}</span>
</md-slider-container>
</div>
</div>
<!-- send appointment notifications -->
<md-checkbox
ng-model="editor.component.sendAppointmentNotifications"
ng-true-value="1"
ng-false-value="0"
label:aria-label="Send Appointment Notifications">
<var:string label:value="Send Appointment Notifications"/>
</md-checkbox>
<!-- attach urls -->
<div ng-repeat="attach in editor.component.attachUrls">
<div layout="row" layout-align="start center">
<md-button class="md-icon-button" type="button" ng-click="editor.component.deleteAttachUrl($index)">
<md-icon>remove_circle</md-icon>
</md-button>
<md-input-container class="md-block md-flex">
<label>
<var:string label:value="URL"/>
</label>
<input type="url" ng-model="attach.value"
sg-focus-on="attachUrl_{{$index}}"/>
</md-input-container>
</div>
</div>
<div layout="row" layout-align="start center">
<md-button class="md-icon-button" type="button" ng-click="editor.addAttachUrl($event)">
<md-icon>add_circle</md-icon>
</md-button>
<label class="button-label">
<var:string label:value="Attach"/>
</label>
</div>
</div>
<div class="sg-form-section">
<!-- is all day -->
<div layout="row">
<md-checkbox flex="50"
ng-model="editor.component.isAllDay"
ng-change="editor.adjustAllDay()"
ng-true-value="1"
ng-false-value="0"
label:aria-label="All day Event">
<var:string label:value="All day Event"/>
</md-checkbox>
<!-- is transparent -->
<md-checkbox flex="50"
ng-model="editor.component.isTransparent"
ng-true-value="1"
ng-false-value="0"
label:aria-label="Show Time as Free">
<var:string label:value="Show Time as Free"/>
</md-checkbox>
</div>
<!-- start + end -->
<div layout="row">
<div flex="50">
<div class="pseudo-input-container">
<label class="pseudo-input-label"><var:string label:value="DateFrom"
/></label>
</div>
<div layout="row" layout-align="start end" layout-wrap="layout-wrap">
<md-datepicker ng-model="editor.component.start"
required="required"
ng-change="editor.adjustStartTime()"
label:md-placeholder="DateFrom"
><!-- date picker --></md-datepicker>
<sg-timepicker ng-model="editor.startTime"
ng-change="editor.adjustStartTime()"
ng-required="!editor.component.isAllDay"
ng-hide="editor.component.isAllDay"><!-- time picker --></sg-timepicker>
</div>
</div>
<div flex="50">
<div class="pseudo-input-container">
<label class="pseudo-input-label"><var:string label:value="DateTo"
/></label>
</div>
<div layout="row" layout-align="start end" layout-wrap="layout-wrap">
<md-datepicker ng-model="editor.component.end"
required="required"
ng-change="editor.adjustEndTime()"
md-min-date="editor.component.start"
label:md-placeholder="DateTo"
><!-- date picker --></md-datepicker>
<sg-timepicker ng-model="editor.endTime"
ng-change="editor.adjustEndTime()"
ng-required="!editor.component.isAllDay"
ng-hide="editor.component.isAllDay"><!-- time picker --></sg-timepicker>
</div>
</div>
</div>
</div>
<!-- repeat -->
<div class="sg-form-section">
<div layout="row" layout-align="start center">
<md-input-container class="md-block md-flex">
<label><var:string label:value="Repeat"/></label>
<md-select
ng-model="editor.component.repeat.frequency"
ng-change="editor.changeFrequency($event)"
ng-disabled="editor.component.occurrenceId">
<md-option ng-value="frequency[0]" ng-repeat="frequency in ::editor.frequencies()" ng-bind-html="frequency[1]"><!-- frequency --></md-option>
</md-select>
</md-input-container>
<md-button type="button" class="sg-icon-button"
label:aria-label="repeat_CUSTOM"
ng-disabled="editor.component.occurrenceId"
ng-show="editor.component.repeat.frequency != 'never'"
ng-click="editor.toggleRecurrenceEditor()">
<md-icon ng-class="{'md-rotate-45': editor.showRecurrenceEditor}">add</md-icon>
</md-button>
</div>
<div flex-offset="5"
ng-show="editor.showRecurrenceEditor">
<var:component className="UIxRecurrenceEditor" />
</div>
<!-- end repeat -->
<div layout="row"
ng-if="editor.component.repeat.frequency != 'never'">
<md-input-container class="md-block" flex="50" flex-xs="100">
<label><var:string label:value="End Repeat"/></label>
<md-select ng-model="editor.component.repeat.end"
ng-disabled="editor.component.occurrenceId">
<md-option value="never"><var:string label:value="Never"/></md-option>
<md-option value="count"><var:string label:value="After"/></md-option>
<md-option value="until"><var:string label:value="On Date"/></md-option>
</md-select>
</md-input-container>
<md-input-container class="md-block md-input-number"
ng-show="editor.component.repeat.end == 'count'">
<label><var:string label:value="times"/></label>
<input type="number" min="1" label:aria-label="times" ng-model="editor.component.repeat.count"/>
</md-input-container>
<md-input-container ng-show="editor.component.repeat.end == 'until'">
<md-datepicker name="repeat_until"
ng-model="editor.component.repeat.until"
md-min-date="editor.component.end"
label:md-placeholder="On Date"><!-- date picker --></md-datepicker>
<div ng-messages="eventForm.repeat_until.$error">
<div ng-message="mindate"><var:string label:value="validate_untilbeforeend"/></div>
</div>
</md-input-container>
</div>
</div>
<!-- attendees -->
<div class="sg-form-section">
<div layout="row" layout-align="start center">
<md-icon>search</md-icon>
<md-autocomplete
class="md-flex"
md-menu-class="md-2-line"
label:md-floating-label="Invite Attendees"
md-search-text="editor.searchText"
md-selected-item-change="editor.addAttendee(card, editor.searchText)"
md-items="card in editor.cardFilter(editor.searchText)"
md-item-text="card.empty"
md-autoselect="true"
var:md-min-length="minimumSearchLength"
md-delay="150"
md-no-cache="true"
sg-enter="editor.addAttendee(editor.searchText)">
<md-item-template>
<div class="sg-tile-content">
<div class="sg-md-subhead md-block" md-highlight-text="editor.searchText" md-highlight-flags="gi">{{ card.$shortFormat(editor.searchText) }}</div>
<div class="sg-md-body" md-colors="::{color: 'default-background-500'}">{{ card.containername }}</div>
</div>
</md-item-template>
</md-autocomplete>
<md-button type="button" class="sg-icon-button"
label:aria-label="repeat_CUSTOM"
ng-show="editor.component.attendees.length"
ng-click="editor.toggleAttendeesEditor()">
<md-icon ng-class="{'md-rotate-180': editor.showAttendeesEditor}">expand_more</md-icon>
</md-button>
</div>
<div ng-show="editor.component.attendees.length">
<!-- md-contact-chips don't support "readonly", so we build them using md-chips
in readonly mode and a template similar to the one of md-contact-chips -->
<md-chips class="ng-hide md-contact-chips sg-readonly attendees-chips"
ng-model="editor.component.attendees"
ng-hide="editor.showAttendeesEditor"
readonly="true">
<md-chip-template>
<div class="md-contact-avatar">
<sg-avatar-image sg-email="$chip.email" size="26">{{ editor.defaultIconForAttendee($chip) }}</sg-avatar-image>
</div>
<div class="md-contact-name">{{$chip.name || $chip.email}}</div>
<md-icon ng-class="'icon-' + $chip.partstat"><!-- partstat --></md-icon>
<md-icon class="sg-chip-remove"
label:aria-label="Delete"
ng-click="editor.removeAttendee($chip, eventForm)">close</md-icon>
</md-chip-template>
</md-chips>
<div class="ng-hide attendees" ng-show="editor.showAttendeesEditor">
<var:component className="UIxAttendeesEditor" />
</div>
</div>
</div>
<div class="sg-form-section">
<!-- reminder -->
<md-checkbox ng-model="editor.component.$hasAlarm"
label:aria-label="Reminder">
<var:string label:value="Reminder"/>
</md-checkbox>
<div flex-offset="5"
ng-if="editor.component.$hasAlarm">
<var:component className="UIxReminderEditor" />
</div>
</div>
</md-dialog-content>
<!-- cancel/reset/save -->
<md-dialog-actions class="ng-hide" ng-hide="editor.attendeeConflictError">
<md-button type="button" ng-click="editor.cancel(eventForm)">
<var:string label:value="Cancel"/>
</md-button>
<md-button type="button"
ng-disabled="eventForm.$pristine"
ng-click="editor.reset(eventForm)">
<var:string label:value="Reset"/>
</md-button>
<md-button class="md-primary" type="submit"
ng-click="editor.save(eventForm)"
ng-disabled="eventForm.$invalid || eventForm.$submitted">
<var:string label:value="Save"/>
</md-button>
</md-dialog-actions>
<!-- attendee availability conflict -->
<md-dialog-content class="md-default-theme md-bg md-warn md-padding sg-dialog-message ng-hide"
ng-show="editor.attendeeConflictError.conflicts">
<div layout="row" layout-align="space-between start" layout-fill="layout-fill">
<div class="sg-padded--bottom" ng-bind-html="::'A time conflict exists with one or more attendees.\nWould you like to keep the current settings anyway?' | loc | txt2html"><!-- warning --></div>
<md-button class="md-icon-button" ng-click="editor.edit(eventForm)">
<md-icon label:aria-label="Close">close</md-icon>
</md-button>
</div>
<div class="md-flex" ng-show="editor.attendeeConflictError.attendee_email">
<md-icon>person</md-icon> {{editor.attendeeConflictError.attendee_name}} ({{editor.attendeeConflictError.attendee_email}})
</div>
<div layout="row"
ng-show="editor.attendeeConflictError.conflicts"
ng-repeat="conflict in editor.attendeeConflictError.conflicts">
<md-icon>schedule</md-icon>
<div class="pseudo-input-container">
<label class="pseudo-input-label"><var:string label:value="DateFrom"
/></label>
<div>{{conflict.startDate}} <md-icon>trending_flat</md-icon></div>
</div>
<div class="pseudo-input-container md-flex">
<label class="pseudo-input-label"><var:string label:value="DateTo"
/></label>
<div>{{conflict.endDate}}</div>
</div>
</div>
</md-dialog-content>
<md-dialog-actions class="ng-hide" ng-show="editor.attendeeConflictError.conflicts">
<md-button type="button" ng-click="editor.cancel(eventForm)">
<var:string label:value="Cancel"/>
</md-button>
<md-button type="button"
ng-click="editor.edit(eventForm)">
<var:string label:value="Edit"/>
</md-button>
<md-button class="md-warn" type="submit"
ng-click="editor.save(eventForm, { ignoreConflicts: true })"
ng-disabled="eventForm.$invalid || eventForm.$submitted">
<var:string label:value="Save"/>
</md-button>
</md-dialog-actions>
<!-- rejected attendee invitation
possible causes:
* maximum number of simultaneous bookings is reached for a resource (fatal error, can't save)
* invitation is prohibited for all attendes (fatal error, can't save)
* invitation is prohibited for a fraction of the attendees (warning, can force saving)
-->
<md-dialog-content class="md-default-theme md-bg md-warn md-padding sg-dialog-message ng-hide" layout="column"
ng-show="editor.attendeeConflictError.reject">
<div layout="row" layout-align="space-between start" layout-fill="layout-fill">
<div ng-bind-html="editor.attendeeConflictError.reject | txt2html"><!-- error --></div>
<md-button class="md-icon-button" ng-click="editor.edit(eventForm)">
<md-icon label:aria-label="Close">close</md-icon>
</md-button>
</div>
<div class="sg-padded--top" ng-show="editor.attendeeConflictError.unavailableAttendees">
<var:string label:value="Would you like to remove them and send the invitation to the remaining attendees?"/>
</div>
</md-dialog-content>
<md-dialog-actions ng-show="editor.attendeeConflictError.reject">
<md-button type="button" ng-click="editor.cancel(eventForm)">
<var:string label:value="Cancel"/>
</md-button>
<md-button type="button"
ng-click="editor.edit(eventForm)">
<var:string label:value="Edit"/>
</md-button>
<md-button class="md-warn" type="submit"
ng-show="editor.attendeeConflictError.unavailableAttendees"
ng-click="editor.save(eventForm, { removeAttendees: editor.attendeeConflictError.unavailableAttendees })"
ng-disabled="eventForm.$invalid || eventForm.$submitted">
<var:string label:value="Save"/>
</md-button>
</md-dialog-actions>
</form>
</md-dialog>
</container>