diff --git a/src/main/java/org/olat/course/nodes/appointments/ui/AppointmentListController.java b/src/main/java/org/olat/course/nodes/appointments/ui/AppointmentListController.java index f50283063accdbbba09e15a825188e7f2be4f790..69e05f468aed54dc22c83229007da45bbb0572b8 100644 --- a/src/main/java/org/olat/course/nodes/appointments/ui/AppointmentListController.java +++ b/src/main/java/org/olat/course/nodes/appointments/ui/AppointmentListController.java @@ -35,6 +35,8 @@ import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.date.DateComponentFactory; import org.olat.core.gui.components.date.DateElement; +import org.olat.core.gui.components.dropdown.DropdownItem; +import org.olat.core.gui.components.dropdown.DropdownOrientation; import org.olat.core.gui.components.form.flexible.FormItem; import org.olat.core.gui.components.form.flexible.FormItemContainer; import org.olat.core.gui.components.form.flexible.elements.FlexiTableElement; @@ -89,7 +91,8 @@ public abstract class AppointmentListController extends FormBasicController impl private static final String CMD_EDIT = "edit"; private FormLink backLink; - private FormLink addAppointmentLink; + private FormLink addSingleAppointmentLink; + private FormLink addRecurringAppointmentLink; private FlexiTableElement tableEl; private AppointmentDataModel dataModel; @@ -98,6 +101,7 @@ public abstract class AppointmentListController extends FormBasicController impl private DialogBoxController confirmParticipationCrtl; private FindingConfirmationController findingConfirmationCtrl; private AppointmentEditController appointmentEditCtrl; + private RecurringAppointmentsController recurringAppointmentsCtrl; private UserSearchController userSearchCtrl; private ParticipationRemoveController removeCtrl; private AppointmentDeleteController appointmentDeleteCtrl; @@ -135,8 +139,8 @@ public abstract class AppointmentListController extends FormBasicController impl protected abstract List<AppointmentRow> loadModel(); protected void setAddAppointmentVisible(boolean visible) { - if (addAppointmentLink != null) { - addAppointmentLink.setVisible(visible); + if (addSingleAppointmentLink != null) { + addSingleAppointmentLink.setVisible(visible); } } @@ -165,8 +169,13 @@ public abstract class AppointmentListController extends FormBasicController impl List<Organizer> organizers = appointmentsService.getOrganizers(topic); if (secCallback.canEditAppointment(organizers)) { - addAppointmentLink = uifactory.addFormLink("add.appointment", topButtons, Link.BUTTON); - addAppointmentLink.setIconLeftCSS("o_icon o_icon-lg o_icon_add"); + DropdownItem addAppointmentDropdown = uifactory.addDropdownMenu("add.appointment", "add.appointment", topButtons, getTranslator()); + addAppointmentDropdown.setOrientation(DropdownOrientation.right); + + addSingleAppointmentLink = uifactory.addFormLink("add.appointment.single", formLayout, Link.LINK); + addAppointmentDropdown.addElement(addSingleAppointmentLink); + addRecurringAppointmentLink = uifactory.addFormLink("add.appointment.recurring", formLayout, Link.LINK); + addAppointmentDropdown.addElement(addRecurringAppointmentLink); } } @@ -267,7 +276,7 @@ public abstract class AppointmentListController extends FormBasicController impl } private void initSorters() { - List<FlexiTableSort> sorters = new ArrayList<>(8); + List<FlexiTableSort> sorters = new ArrayList<>(2); sorters.add(new FlexiTableSort(translate(AppointmentCols.start.i18nHeaderKey()), AppointmentCols.start.name())); sorters.add(new FlexiTableSort(translate(AppointmentCols.numberOfParticipations.i18nHeaderKey()), AppointmentCols.numberOfParticipations.name())); FlexiTableSortOptions options = new FlexiTableSortOptions(sorters); @@ -396,8 +405,10 @@ public abstract class AppointmentListController extends FormBasicController impl protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { if (source == backLink) { fireEvent(ureq, Event.DONE_EVENT); - } else if (source == addAppointmentLink) { - doAddAppointment(ureq); + } else if (source == addSingleAppointmentLink) { + doAddSingleAppointment(ureq); + } else if (source == addRecurringAppointmentLink) { + doAddRecurringAppointment(ureq); } else if (source instanceof FormLink) { FormLink link = (FormLink)source; String cmd = link.getCmd(); @@ -445,6 +456,12 @@ public abstract class AppointmentListController extends FormBasicController impl } cmc.deactivate(); cleanUp(); + } else if (recurringAppointmentsCtrl == source) { + if (event == Event.DONE_EVENT) { + updateModel(); + } + cmc.deactivate(); + cleanUp(); } else if (appointmentDeleteCtrl == source) { if (event == Event.DONE_EVENT) { updateModel(); @@ -481,12 +498,14 @@ public abstract class AppointmentListController extends FormBasicController impl } private void cleanUp() { + removeAsListenerAndDispose(recurringAppointmentsCtrl); removeAsListenerAndDispose(findingConfirmationCtrl); removeAsListenerAndDispose(appointmentDeleteCtrl); removeAsListenerAndDispose(appointmentEditCtrl); removeAsListenerAndDispose(userSearchCtrl); removeAsListenerAndDispose(removeCtrl); removeAsListenerAndDispose(cmc); + recurringAppointmentsCtrl = null; findingConfirmationCtrl = null; appointmentDeleteCtrl = null; appointmentEditCtrl = null; @@ -528,7 +547,7 @@ public abstract class AppointmentListController extends FormBasicController impl } } - private void doAddAppointment(UserRequest ureq) { + private void doAddSingleAppointment(UserRequest ureq) { appointmentEditCtrl = new AppointmentEditController(ureq, getWindowControl(), topic); listenTo(appointmentEditCtrl); @@ -538,6 +557,16 @@ public abstract class AppointmentListController extends FormBasicController impl cmc.activate(); } + private void doAddRecurringAppointment(UserRequest ureq) { + recurringAppointmentsCtrl = new RecurringAppointmentsController(ureq, getWindowControl(), topic); + listenTo(recurringAppointmentsCtrl); + + cmc = new CloseableModalController(getWindowControl(), "close", recurringAppointmentsCtrl.getInitialComponent(), true, + translate("add.appointment.recurring")); + listenTo(cmc); + cmc.activate(); + } + private void doEditAppointment(UserRequest ureq, Appointment appointment) { appointmentEditCtrl = new AppointmentEditController(ureq, getWindowControl(), appointment); listenTo(appointmentEditCtrl); diff --git a/src/main/java/org/olat/course/nodes/appointments/ui/RecurringAppointmentsController.java b/src/main/java/org/olat/course/nodes/appointments/ui/RecurringAppointmentsController.java new file mode 100644 index 0000000000000000000000000000000000000000..8fee5eb9fa53b5cb743a26c2ef4864342d1f3c3c --- /dev/null +++ b/src/main/java/org/olat/course/nodes/appointments/ui/RecurringAppointmentsController.java @@ -0,0 +1,208 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.appointments.ui; + +import static org.olat.core.gui.components.util.KeyValues.entry; + +import java.time.DayOfWeek; +import java.time.format.TextStyle; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.DateChooser; +import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement; +import org.olat.core.gui.components.form.flexible.elements.TextElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.components.util.KeyValues; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.util.DateUtils; +import org.olat.core.util.StringHelper; +import org.olat.course.nodes.appointments.Appointment; +import org.olat.course.nodes.appointments.AppointmentsService; +import org.olat.course.nodes.appointments.Topic; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 26 Jun 2020<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class RecurringAppointmentsController extends FormBasicController { + + private TextElement locationEl; + private TextElement maxParticipationsEl; + private DateChooser recurringFirstEl; + private MultipleSelectionElement recurringDaysOfWeekEl; + private DateChooser recurringLastEl; + + private final Topic topic; + + @Autowired + private AppointmentsService appointmentsService; + + public RecurringAppointmentsController(UserRequest ureq, WindowControl wControl, Topic topic) { + super(ureq, wControl); + this.topic = topic; + + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + locationEl = uifactory.addTextElement("appointment.location", 128, null, formLayout); + locationEl.setHelpTextKey("appointment.init.value", null); + + maxParticipationsEl = uifactory.addTextElement("appointment.max.participations", 5, null, formLayout); + maxParticipationsEl.setHelpTextKey("appointment.init.value", null); + + recurringFirstEl = uifactory.addDateChooser("appointments.recurring.first", null, formLayout); + recurringFirstEl.setDateChooserTimeEnabled(true); + recurringFirstEl.setSecondDate(true); + recurringFirstEl.setSameDay(true); + recurringFirstEl.setMandatory(true); + + DayOfWeek[] dayOfWeeks = DayOfWeek.values(); + KeyValues dayOfWeekKV = new KeyValues(); + for (int i = 0; i < dayOfWeeks.length; i++) { + dayOfWeekKV.add(entry(dayOfWeeks[i].name(), dayOfWeeks[i].getDisplayName(TextStyle.FULL_STANDALONE, getLocale()))); + } + recurringDaysOfWeekEl = uifactory.addCheckboxesHorizontal("appointments.recurring.days.of.week", formLayout, + dayOfWeekKV.keys(), dayOfWeekKV.values()); + recurringDaysOfWeekEl.setMandatory(true); + + recurringLastEl = uifactory.addDateChooser("appointments.recurring.last", null, formLayout); + recurringLastEl.setMandatory(true); + + // Buttons + FormLayoutContainer buttonsCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator()); + formLayout.add(buttonsCont); + buttonsCont.setRootForm(mainForm); + uifactory.addFormSubmitButton("save", buttonsCont); + uifactory.addFormCancelButton("cancel", buttonsCont, ureq, getWindowControl()); + } + + @Override + protected boolean validateFormLogic(UserRequest ureq) { + boolean allOk = super.validateFormLogic(ureq); + + maxParticipationsEl.clearError(); + String maxParticipationsValue = maxParticipationsEl.getValue(); + if (maxParticipationsEl.isVisible() && StringHelper.containsNonWhitespace(maxParticipationsValue)) { + try { + int value = Integer.parseInt(maxParticipationsValue); + if (value < 1) { + maxParticipationsEl.setErrorKey("error.positiv.number", null); + allOk &= false; + } + } catch (NumberFormatException e) { + maxParticipationsEl.setErrorKey("error.positiv.number", null); + allOk &= false; + } + } + + recurringFirstEl.clearError(); + recurringDaysOfWeekEl.clearError(); + recurringLastEl.clearError(); + if (recurringFirstEl.getDate() == null || recurringFirstEl.getSecondDate() == null) { + recurringFirstEl.setErrorKey("form.legende.mandatory", null); + allOk &= false; + } else if (recurringFirstEl.getDate().after(recurringFirstEl.getSecondDate())) { + recurringFirstEl.setErrorKey("error.start.after.end", null); + allOk &= false; + } + + if (!recurringDaysOfWeekEl.isAtLeastSelected(1)) { + recurringDaysOfWeekEl.setErrorKey("form.legende.mandatory", null); + allOk &= false; + } + + if (recurringLastEl.getDate() == null) { + recurringLastEl.setErrorKey("form.legende.mandatory", null); + allOk &= false; + } + + if (recurringFirstEl.getDate() != null && recurringLastEl.getDate() != null + && recurringFirstEl.getDate().after(recurringLastEl.getDate())) { + recurringLastEl.setErrorKey("error.first.after.start", null); + allOk &= false; + } + + return allOk; + } + + @Override + protected void formCancelled(UserRequest ureq) { + fireEvent(ureq, Event.CANCELLED_EVENT); + } + + @Override + protected void formOK(UserRequest ureq) { + doSaveReccuringAppointments(); + fireEvent(ureq, Event.DONE_EVENT); + } + + private void doSaveReccuringAppointments() { + Date firstStart = recurringFirstEl.getDate(); + Date firstEnd = recurringFirstEl.getSecondDate(); + Date last = recurringLastEl.getDate(); + last = DateUtils.setTime(last, 23, 59, 59); + + Collection<DayOfWeek> daysOfWeek = recurringDaysOfWeekEl.getSelectedKeys().stream() + .map(DayOfWeek::valueOf) + .collect(Collectors.toList()); + + List<Date> starts = DateUtils.getDaysInRange(firstStart, last, daysOfWeek); + for (Date start : starts) { + Appointment appointment = appointmentsService.createUnsavedAppointment(topic); + + appointment.setStart(start); + + Date end = DateUtils.copyTime(start, firstEnd); + appointment.setEnd(end); + + String location = locationEl.getValue(); + appointment.setLocation(location); + + if (maxParticipationsEl.isVisible()) { + String maxParticipationsValue = maxParticipationsEl.getValue(); + Integer maxParticipations = StringHelper.containsNonWhitespace(maxParticipationsValue) + ? Integer.valueOf(maxParticipationsValue) + : null; + appointment.setMaxParticipations(maxParticipations); + } + + appointmentsService.saveAppointment(appointment); + } + } + + @Override + protected void doDispose() { + // + } + +} diff --git a/src/main/java/org/olat/course/nodes/appointments/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/appointments/ui/_i18n/LocalStrings_de.properties index f0a3c1491454aa3383785f7c3bebd93a9c989679..7162825d087b256c9b6861e6bfcc4623a25b2582 100644 --- a/src/main/java/org/olat/course/nodes/appointments/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/course/nodes/appointments/ui/_i18n/LocalStrings_de.properties @@ -1,5 +1,7 @@ add.appointment=Termin hinzuf\u00fcgen add.appointment.button=Hinzuf\u00fcgen +add.appointment.recurring=Wiederkehrende Termine hinzuf\u00fcgen +add.appointment.single=Einzelnen Termin hinzuf\u00fcgen add.appointment.title=Termin hinzuf\u00fcgen add.topic=Termine hinzuf\u00fcgen add.topic.title=$:\add.topic diff --git a/src/main/java/org/olat/course/nodes/appointments/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/appointments/ui/_i18n/LocalStrings_en.properties index 726d3b9a9ab55697fad162b876d0fc52ffac3ba0..521802b15bec902e21889f9f67f77a5bd00d30a3 100644 --- a/src/main/java/org/olat/course/nodes/appointments/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/course/nodes/appointments/ui/_i18n/LocalStrings_en.properties @@ -1,5 +1,7 @@ add.appointment=Add appointment add.appointment.button=Add +add.appointment.recurring=Add recurring appointments +add.appointment.single=Add single appointment add.appointment.title=Add appointment add.topic=Add appointments add.topic.title=$:\add.topic