diff --git a/src/main/java/org/olat/core/util/DateUtils.java b/src/main/java/org/olat/core/util/DateUtils.java index 3786636b3283fbe54a40e020fa52b42323bfd93c..7da4c0386ad19edabf8de95ada24cdb5c33e9060 100644 --- a/src/main/java/org/olat/core/util/DateUtils.java +++ b/src/main/java/org/olat/core/util/DateUtils.java @@ -63,11 +63,41 @@ public class DateUtils { return Instant.ofEpochMilli(date.getTime()).atZone(ZoneId.systemDefault()).toLocalDateTime(); } + public static boolean isSameDate(Date date1, Date date2) { + Calendar cal1 = Calendar.getInstance(); + cal1.setTime(date1); + Calendar cal2 = Calendar.getInstance(); + cal2.setTime(date2); + return isSameDate(cal1, cal2); + } + + protected static boolean isSameDate(Calendar cal1, Calendar cal2) { + return isSameDay(cal1, cal2) && isSameTime(cal1, cal2); + } + + public static boolean isSameDay(Date date1, Date date2) { + Calendar cal1 = Calendar.getInstance(); + cal1.setTime(date1); + Calendar cal2 = Calendar.getInstance(); + cal2.setTime(date2); + return isSameDay(cal1, cal2); + } + + public static boolean isSameDay(Calendar cal1, Calendar cal2) { + return (cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA) && + cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) && + cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR)); + } + public static boolean isSameTime(Date date1, Date date2) { Calendar cal1 = Calendar.getInstance(); cal1.setTime(date1); Calendar cal2 = Calendar.getInstance(); cal2.setTime(date2); + return isSameTime(cal1, cal2); + } + + public static boolean isSameTime(Calendar cal1, Calendar cal2) { return (cal1.get(Calendar.SECOND) == cal2.get(Calendar.SECOND) && cal1.get(Calendar.MINUTE) == cal2.get(Calendar.MINUTE) && cal1.get(Calendar.HOUR) == cal2.get(Calendar.HOUR)); diff --git a/src/main/java/org/olat/course/nodes/appointments/ui/AppointmentDataModel.java b/src/main/java/org/olat/course/nodes/appointments/ui/AppointmentDataModel.java index cc7b3ee2a31b35f0651d9602732282386785879d..c0794946d9a777d0eeed20a001be4df7ddf0054e 100644 --- a/src/main/java/org/olat/course/nodes/appointments/ui/AppointmentDataModel.java +++ b/src/main/java/org/olat/course/nodes/appointments/ui/AppointmentDataModel.java @@ -19,15 +19,20 @@ */ package org.olat.course.nodes.appointments.ui; +import java.util.Date; import java.util.List; import java.util.Locale; +import java.util.stream.Collectors; import org.olat.core.commons.persistence.SortKey; +import org.olat.core.gui.components.form.flexible.elements.FlexiTableFilter; import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableDataModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FilterableFlexiTableModel; import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiSortableColumnDef; import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel; import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableDataModel; import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableModelDelegate; +import org.olat.core.util.DateUtils; /** * @@ -36,9 +41,14 @@ import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFl * */ public class AppointmentDataModel extends DefaultFlexiTableDataModel<AppointmentRow> -implements SortableFlexiTableDataModel<AppointmentRow> { +implements SortableFlexiTableDataModel<AppointmentRow>, FilterableFlexiTableModel { + + public static final String FILTER_ALL = "all"; + public static final String FILTER_PARTICIPATED = "participated"; + public static final String FILTER_FUTURE = "future"; private final Locale locale; + private List<AppointmentRow> backups; public AppointmentDataModel(FlexiTableColumnModel columnsModel, Locale locale) { super(columnsModel); @@ -50,6 +60,42 @@ implements SortableFlexiTableDataModel<AppointmentRow> { List<AppointmentRow> rows = new SortableFlexiTableModelDelegate<>(orderBy, this, locale).sort(); super.setObjects(rows); } + + @Override + public void setObjects(List<AppointmentRow> objects) { + super.setObjects(objects); + backups = objects; + } + + @Override + public void filter(String searchString, List<FlexiTableFilter> filters) { + if (filters == null || filters.isEmpty() || FILTER_ALL.equals(filters.get(0).getFilter())) { + super.setObjects(backups); + } else { + List<String> filterKeys = filters.stream().map(FlexiTableFilter::getFilter).collect(Collectors.toList()); + Date now = new Date(); + List<AppointmentRow> filteredRows = backups.stream() + .filter(r -> apply(filterKeys, r, now)) + .collect(Collectors.toList()); + super.setObjects(filteredRows); + } + } + + private boolean apply(List<String> filterKeys, AppointmentRow row, Date date) { + if (filterKeys.contains(FILTER_PARTICIPATED) && row.getParticipation() != null) { + return true; + } + if (filterKeys.contains(FILTER_FUTURE)) { + Date end = row.getAppointment().getEnd(); + end = DateUtils.isSameDate(end, row.getAppointment().getStart()) + ? DateUtils.setTime(end, 23, 59, 59) + : end; + if (date.before(end)) { + return true; + } + } + return false; + } @Override public Object getValueAt(int row, int col) { 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 aebe9e86cab11a06f4a82681ca73a79264fb2bf1..69d7831f2c7f99070e975ebffd08058001a433c1 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 @@ -24,7 +24,6 @@ import java.util.Date; import java.util.List; import java.util.Locale; -import org.apache.commons.lang.time.DateUtils; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.date.DateComponentFactory; @@ -32,6 +31,7 @@ import org.olat.core.gui.components.date.DateElement; 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; +import org.olat.core.gui.components.form.flexible.elements.FlexiTableFilter; import org.olat.core.gui.components.form.flexible.elements.FormLink; import org.olat.core.gui.components.form.flexible.impl.FormBasicController; import org.olat.core.gui.components.form.flexible.impl.FormEvent; @@ -49,6 +49,7 @@ import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; import org.olat.core.gui.control.generic.modal.DialogBoxController; import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; +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.Appointment.Status; @@ -115,6 +116,10 @@ public abstract class AppointmentListController extends FormBasicController impl protected abstract String getTableCssClass(); + protected abstract List<String> getFilters(); + + protected abstract List<String> getDefaultFilters(); + protected abstract String getPersistedPreferencesId(); protected abstract List<AppointmentRow> loadModel(); @@ -210,6 +215,39 @@ public abstract class AppointmentListController extends FormBasicController impl VelocityContainer rowVC = createVelocityContainer("appointment_row"); rowVC.setDomReplacementWrapperRequired(false); tableEl.setRowRenderer(rowVC, this); + + initFilters(); + } + + protected void initFilters() { + List<String> filters = getFilters(); + if (filters != null && !filters.isEmpty()) { + List<FlexiTableFilter> tableFilters = new ArrayList<>(3); + List<String> defaultFilters = getDefaultFilters(); + List<FlexiTableFilter> selectedFilters = new ArrayList<>(defaultFilters.size()); + if (filters.contains(AppointmentDataModel.FILTER_PARTICIPATED)) { + FlexiTableFilter filter = new FlexiTableFilter(translate("filter.participated"), AppointmentDataModel.FILTER_PARTICIPATED, false); + tableFilters.add(filter); + if (defaultFilters.contains(AppointmentDataModel.FILTER_PARTICIPATED)) { + selectedFilters.add(filter); + } + } + if (filters.contains(AppointmentDataModel.FILTER_FUTURE)) { + FlexiTableFilter filter = new FlexiTableFilter(translate("filter.future"), AppointmentDataModel.FILTER_FUTURE, false); + tableFilters.add(filter); + if (defaultFilters.contains(AppointmentDataModel.FILTER_FUTURE)) { + selectedFilters.add(filter); + } + } + tableFilters.add(FlexiTableFilter.SPACER); + FlexiTableFilter filter = new FlexiTableFilter(translate("filter.all"), AppointmentDataModel.FILTER_ALL, true); + tableFilters.add(filter); + if (defaultFilters.contains(AppointmentDataModel.FILTER_ALL)) { + selectedFilters.add(filter); + } + tableEl.setFilters("Filters", tableFilters, true); + tableEl.setSelectedFilters(selectedFilters); + } } private void updateModel() { @@ -229,7 +267,7 @@ public abstract class AppointmentListController extends FormBasicController impl String time = null; boolean sameDay = DateUtils.isSameDay(begin, end); - boolean sameTime = org.olat.core.util.DateUtils.isSameTime(begin, end); + boolean sameTime = DateUtils.isSameTime(begin, end); String startDate = StringHelper.formatLocaleDateFull(begin.getTime(), locale); String startTime = StringHelper.formatLocaleTime(begin.getTime(), locale); String endDate = StringHelper.formatLocaleDateFull(end.getTime(), locale); diff --git a/src/main/java/org/olat/course/nodes/appointments/ui/AppointmentListEditController.java b/src/main/java/org/olat/course/nodes/appointments/ui/AppointmentListEditController.java index eb5416ad888574c13d522e93409fe7630c441115..1260e7f2c53603e2a01e83e284a6e7666011dbcb 100644 --- a/src/main/java/org/olat/course/nodes/appointments/ui/AppointmentListEditController.java +++ b/src/main/java/org/olat/course/nodes/appointments/ui/AppointmentListEditController.java @@ -22,6 +22,7 @@ package org.olat.course.nodes.appointments.ui; import static java.util.Collections.emptyList; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -63,6 +64,16 @@ public class AppointmentListEditController extends AppointmentListController { return "o_edit"; } + @Override + protected List<String> getFilters() { + return Collections.singletonList(AppointmentDataModel.FILTER_FUTURE); + } + + @Override + protected List<String> getDefaultFilters() { + return Collections.singletonList(AppointmentDataModel.FILTER_ALL); + } + @Override protected String getPersistedPreferencesId() { return "ap-appointment-edit"; diff --git a/src/main/java/org/olat/course/nodes/appointments/ui/AppointmentListSelectionController.java b/src/main/java/org/olat/course/nodes/appointments/ui/AppointmentListSelectionController.java index af347a9ec3979ecfaa7ca11ab54b55a086fdf74e..954748f6e7c1ef211c103fefebfaa97c9ffceae5 100644 --- a/src/main/java/org/olat/course/nodes/appointments/ui/AppointmentListSelectionController.java +++ b/src/main/java/org/olat/course/nodes/appointments/ui/AppointmentListSelectionController.java @@ -22,6 +22,7 @@ package org.olat.course.nodes.appointments.ui; import static java.util.Collections.emptyList; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Optional; @@ -65,6 +66,16 @@ public class AppointmentListSelectionController extends AppointmentListControlle return "o_selection"; } + @Override + protected List<String> getFilters() { + return Arrays.asList(AppointmentDataModel.FILTER_PARTICIPATED, AppointmentDataModel.FILTER_FUTURE); + } + + @Override + protected List<String> getDefaultFilters() { + return Arrays.asList(AppointmentDataModel.FILTER_PARTICIPATED, AppointmentDataModel.FILTER_FUTURE); + } + @Override protected String getPersistedPreferencesId() { return "ap-appointment-selection"; 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 0762ca3231edb55df6529757064382deb2be862b..bd123a1b40b022fe2eb6cef31276752a1fd4155d 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 @@ -66,6 +66,9 @@ error.select.appointment=Sie m\u00fcssen einen Termin ausw\u00e4hlen. error.select.participant=Sie m\u00fcssen einen Teilnehmer ausw\u00e4hlen. error.start.after.end=Das Ende darf nicht vor dem Start liegen. error.too.much.participations=Es gibt bereits {0} Teilnahmen. +filter.all=Alle anzeigen +filter.future=Zuk\u00fcnftig +filter.participated=Ausgew\u00e4hlt full.day=Ganzer Tag general=Konfiguration mail.appointments.deleted.body=Liebe/r {0} <br><br>Folgende Termine wurden abgesagt.<br><br>{1} 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 d84fbc900ae532d5f31976d1dd53e902a1c9074a..b00faaa14732f578858d842ba250c38e1e0fd465 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 @@ -65,6 +65,9 @@ error.select.appointment=You have to select an appointment. error.select.participant=You have to select a participant. error.start.after.end=The end date must not be before the start date. error.too.much.participations=There are already {0} participations. +filter.all=Show all +filter.future=Future +filter.participated=Selected full.day=Full day general=Configuration mail.appointments.deleted.body=Dear {0} <br><br>The following appointments were deleted.<br><br>{1}