From 44fd171685cbe736af32df16d30458db9b2af7cb Mon Sep 17 00:00:00 2001 From: srosse <none@none> Date: Fri, 7 Apr 2017 11:30:43 +0200 Subject: [PATCH] OO-2636: database setup, overview list of participants with statistics and custom attendance rate editor --- .../course/run/CourseRuntimeController.java | 2 +- .../lecture/LectureParticipantSummary.java | 4 + .../olat/modules/lecture/LectureService.java | 29 +++ .../lecture/manager/LectureBlockDAO.java | 10 +- .../manager/LectureBlockRollCallDAO.java | 50 ++++ .../manager/LectureParticipantSummaryDAO.java | 21 ++ .../lecture/manager/LectureServiceImpl.java | 28 ++- .../modules/lecture/manager/ReasonDAO.java | 9 + .../lecture/model/LectureBlockImpl.java | 2 +- .../model/LectureParticipantSummaryImpl.java | 12 +- .../model/ParticipantLectureStatistics.java | 78 ++++++ .../ui/EditParticipantRateController.java | 121 +++++++++ .../ui/LectureRepositoryAdminController.java | 22 +- .../lecture/ui/ParticipantListDataModel.java | 100 ++++++++ .../ParticipantListRepositoryController.java | 232 ++++++++++++++++++ .../modules/lecture/ui/ParticipantRow.java | 50 ++++ .../lecture/ui/TeacherOverviewController.java | 36 ++- .../lecture/ui/TeacherRollCallController.java | 212 +++++++++++++++- .../ui/_content/authorized_absence_cell.html | 12 +- .../lecture/ui/_content/date_start_end.html | 7 +- .../_content/participant_list_overview.html | 1 + .../modules/lecture/ui/_content/rollcall.html | 1 + .../ui/_i18n/LocalStrings_de.properties | 92 ++++--- .../ui/_i18n/LocalStrings_en.properties | 92 ++++--- .../LectureBlockStatusCellRenderer.java | 51 ++++ .../LectureStatisticsCellRenderer.java | 46 ++-- .../_spring/userPropertiesContext.xml | 23 ++ .../database/mysql/alter_11_4_x_to_11_5_0.sql | 2 +- .../database/mysql/setupDatabase.sql | 115 +++++++++ .../postgresql/alter_11_4_x_to_11_5_0.sql | 2 +- .../database/postgresql/setupDatabase.sql | 119 +++++++++ 31 files changed, 1437 insertions(+), 144 deletions(-) create mode 100644 src/main/java/org/olat/modules/lecture/model/ParticipantLectureStatistics.java create mode 100644 src/main/java/org/olat/modules/lecture/ui/EditParticipantRateController.java create mode 100644 src/main/java/org/olat/modules/lecture/ui/ParticipantListDataModel.java create mode 100644 src/main/java/org/olat/modules/lecture/ui/ParticipantListRepositoryController.java create mode 100644 src/main/java/org/olat/modules/lecture/ui/ParticipantRow.java create mode 100644 src/main/java/org/olat/modules/lecture/ui/_content/participant_list_overview.html create mode 100644 src/main/java/org/olat/modules/lecture/ui/component/LectureBlockStatusCellRenderer.java diff --git a/src/main/java/org/olat/course/run/CourseRuntimeController.java b/src/main/java/org/olat/course/run/CourseRuntimeController.java index 5280fed1a03..fed10b48f37 100644 --- a/src/main/java/org/olat/course/run/CourseRuntimeController.java +++ b/src/main/java/org/olat/course/run/CourseRuntimeController.java @@ -1371,7 +1371,7 @@ public class CourseRuntimeController extends RepositoryEntryRuntimeController im OLATResourceable ores = OresHelper.createOLATResourceableType("lecturesAdmin"); WindowControl swControl = addToHistory(ureq, ores, null); - LectureRepositoryAdminController ctrl = new LectureRepositoryAdminController(ureq, swControl, toolbarPanel, getRepositoryEntry()); + LectureRepositoryAdminController ctrl = new LectureRepositoryAdminController(ureq, swControl, getRepositoryEntry()); lecturesAdminCtrl = pushController(ureq, translate("command.options.lectures.admin"), ctrl); setActiveTool(lecturesAdminLink); currentToolCtr = lecturesAdminCtrl; diff --git a/src/main/java/org/olat/modules/lecture/LectureParticipantSummary.java b/src/main/java/org/olat/modules/lecture/LectureParticipantSummary.java index effb2209700..a58af2911f2 100644 --- a/src/main/java/org/olat/modules/lecture/LectureParticipantSummary.java +++ b/src/main/java/org/olat/modules/lecture/LectureParticipantSummary.java @@ -31,4 +31,8 @@ import org.olat.core.id.ModifiedInfo; public interface LectureParticipantSummary extends CreateInfo, ModifiedInfo { public Long getKey(); + + public Double getAttendanceRate(); + + public void setAttendanceRate(Double rate); } diff --git a/src/main/java/org/olat/modules/lecture/LectureService.java b/src/main/java/org/olat/modules/lecture/LectureService.java index 9adf3171b28..7a820f7e2de 100644 --- a/src/main/java/org/olat/modules/lecture/LectureService.java +++ b/src/main/java/org/olat/modules/lecture/LectureService.java @@ -26,6 +26,7 @@ import org.olat.basesecurity.IdentityRef; import org.olat.core.id.Identity; import org.olat.modules.lecture.model.LectureBlockAndRollCall; import org.olat.modules.lecture.model.LectureStatistics; +import org.olat.modules.lecture.model.ParticipantLectureStatistics; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryEntryRef; @@ -85,6 +86,14 @@ public interface LectureService { */ public List<Reason> getAllReasons(); + /** + * Load a reason by its primary key. + * + * @param key The primary key + * @return A reason + */ + public Reason getReason(Long key); + /** * * @param title @@ -218,6 +227,18 @@ public interface LectureService { public void removeTeacher(LectureBlock block, Identity teacher); + /** + * The method will not set the date of admission. + * + * @param entry + * @param identity + * @return + */ + public LectureParticipantSummary getOrCreateParticipantSummary(RepositoryEntry entry, Identity identity); + + + public LectureParticipantSummary saveParticipantSummary(LectureParticipantSummary summary); + /** * Returns the statistics for the specified participant. @@ -227,6 +248,14 @@ public interface LectureService { */ public List<LectureStatistics> getParticipantLecturesStatistics(IdentityRef identity); + /** + * Return all the statistics for a course / repository entry. + * + * @param entry The course / repository entry + * @return Statistics per user + */ + public List<ParticipantLectureStatistics> getParticipantsLecturesStatistics(RepositoryEntryRef entry); + /** * * @param entry diff --git a/src/main/java/org/olat/modules/lecture/manager/LectureBlockDAO.java b/src/main/java/org/olat/modules/lecture/manager/LectureBlockDAO.java index 596b82cb391..77ee05f5305 100644 --- a/src/main/java/org/olat/modules/lecture/manager/LectureBlockDAO.java +++ b/src/main/java/org/olat/modules/lecture/manager/LectureBlockDAO.java @@ -75,10 +75,14 @@ public class LectureBlockDAO { } public LectureBlock loadByKey(Long key) { - String q = "select block from lectureblock block where block.key=:blockKey"; - + StringBuilder sb = new StringBuilder(); + sb.append("select block from lectureblock block") + .append(" left join fetch block.reasonEffectiveEnd reason") + .append(" inner join fetch block.entry entry") + .append(" where block.key=:blockKey"); + List<LectureBlock> blocks = dbInstance.getCurrentEntityManager() - .createQuery(q, LectureBlock.class) + .createQuery(sb.toString(), LectureBlock.class) .setParameter("blockKey", key) .getResultList(); return blocks == null || blocks.isEmpty() ? null : blocks.get(0); diff --git a/src/main/java/org/olat/modules/lecture/manager/LectureBlockRollCallDAO.java b/src/main/java/org/olat/modules/lecture/manager/LectureBlockRollCallDAO.java index 53e34bb770e..b6ee28fc843 100644 --- a/src/main/java/org/olat/modules/lecture/manager/LectureBlockRollCallDAO.java +++ b/src/main/java/org/olat/modules/lecture/manager/LectureBlockRollCallDAO.java @@ -36,6 +36,7 @@ import org.olat.modules.lecture.LectureBlockRollCall; import org.olat.modules.lecture.model.LectureBlockAndRollCall; import org.olat.modules.lecture.model.LectureBlockRollCallImpl; import org.olat.modules.lecture.model.LectureStatistics; +import org.olat.modules.lecture.model.ParticipantLectureStatistics; import org.olat.repository.RepositoryEntryRef; import org.olat.user.UserManager; import org.springframework.beans.factory.annotation.Autowired; @@ -280,4 +281,53 @@ public class LectureBlockRollCallDAO { return new ArrayList<>(stats.values()); } + + + public List<ParticipantLectureStatistics> getStatistics(RepositoryEntryRef entry) { + StringBuilder sb = new StringBuilder(); + sb.append("select ident.key, ") + .append(" count(call.lecturesAttendedNumber), count(call.lecturesAbsentNumber),") + .append(" count(block.plannedLecturesNumber), count(block.effectiveLecturesNumber)") + .append(" from lectureblock block") + .append(" inner join block.groups blockToGroup") + .append(" inner join blockToGroup.group bGroup") + .append(" inner join bGroup.members membership") + .append(" inner join membership.identity ident") + .append(" left join lectureblockrollcall as call on (call.identity.key=membership.identity.key and call.lectureBlock.key=block.key)") + .append(" where block.entry.key=:entryKey and membership.role='").append(GroupRoles.participant.name()).append("'") + .append(" group by ident.key"); + + List<Object[]> rawObjects = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), Object[].class) + .setParameter("entryKey", entry.getKey()) + .getResultList(); + Map<Long,ParticipantLectureStatistics> stats = new HashMap<>(); + for(Object[] rawObject:rawObjects) { + int pos = 0;//jump roll call key + Long identityKey = (Long)rawObject[pos++]; + Long attended = PersistenceHelper.extractLong(rawObject, pos++); + Long absent = PersistenceHelper.extractLong(rawObject, pos++); + Long plannedBlocks = PersistenceHelper.extractLong(rawObject, pos++); + + ParticipantLectureStatistics entryStatistics; + if(stats.containsKey(identityKey)) { + entryStatistics = stats.get(identityKey); + } else { + entryStatistics = new ParticipantLectureStatistics(identityKey); + stats.put(identityKey, entryStatistics); + } + + if(absent != null) { + entryStatistics.addTotalAbsentLectures(absent.longValue()); + } + if(attended != null) { + entryStatistics.addTotalAttendedLectures(attended.longValue()); + } + if(plannedBlocks != null) { + entryStatistics.addTotalPlannedLectures(plannedBlocks.longValue()); + } + } + + return new ArrayList<>(stats.values()); + } } diff --git a/src/main/java/org/olat/modules/lecture/manager/LectureParticipantSummaryDAO.java b/src/main/java/org/olat/modules/lecture/manager/LectureParticipantSummaryDAO.java index 3ab15a3914e..9db1bbe5971 100644 --- a/src/main/java/org/olat/modules/lecture/manager/LectureParticipantSummaryDAO.java +++ b/src/main/java/org/olat/modules/lecture/manager/LectureParticipantSummaryDAO.java @@ -24,6 +24,7 @@ import java.util.Date; import java.util.List; import org.olat.basesecurity.GroupRoles; +import org.olat.basesecurity.IdentityRef; import org.olat.core.commons.persistence.DB; import org.olat.core.id.Identity; import org.olat.modules.lecture.LectureBlock; @@ -31,6 +32,7 @@ import org.olat.modules.lecture.LectureParticipantSummary; import org.olat.modules.lecture.model.LectureParticipantSummaryImpl; import org.olat.modules.lecture.model.ParticipantAndLectureSummary; import org.olat.repository.RepositoryEntry; +import org.olat.repository.RepositoryEntryRef; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -84,4 +86,23 @@ public class LectureParticipantSummaryDAO { } return summaries; } + + public LectureParticipantSummary getSummary(RepositoryEntryRef entry, IdentityRef identity) { + StringBuilder sb = new StringBuilder(); + sb.append("select summary from lectureparticipantsummary summary") + .append(" inner join fetch summary.identity ident") + .append(" inner join fetch ident.user identUser") + .append(" where summary.entry.key=:entryKey and ident.key=:identityKey"); + + List<LectureParticipantSummary> summaries = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), LectureParticipantSummary.class) + .setParameter("entryKey", entry.getKey()) + .setParameter("identityKey", identity.getKey()) + .getResultList(); + return summaries == null || summaries.isEmpty() ? null : summaries.get(0); + } + + public LectureParticipantSummary update(LectureParticipantSummary summary) { + return dbInstance.getCurrentEntityManager().merge(summary); + } } diff --git a/src/main/java/org/olat/modules/lecture/manager/LectureServiceImpl.java b/src/main/java/org/olat/modules/lecture/manager/LectureServiceImpl.java index 891f7528424..758167e99d6 100644 --- a/src/main/java/org/olat/modules/lecture/manager/LectureServiceImpl.java +++ b/src/main/java/org/olat/modules/lecture/manager/LectureServiceImpl.java @@ -35,6 +35,7 @@ import org.olat.modules.lecture.LectureBlock; import org.olat.modules.lecture.LectureBlockRef; import org.olat.modules.lecture.LectureBlockRollCall; import org.olat.modules.lecture.LectureBlockToGroup; +import org.olat.modules.lecture.LectureParticipantSummary; import org.olat.modules.lecture.LectureService; import org.olat.modules.lecture.Reason; import org.olat.modules.lecture.RepositoryEntryLectureConfiguration; @@ -42,6 +43,7 @@ import org.olat.modules.lecture.model.LectureBlockAndRollCall; import org.olat.modules.lecture.model.LectureBlockImpl; import org.olat.modules.lecture.model.LectureStatistics; import org.olat.modules.lecture.model.ParticipantAndLectureSummary; +import org.olat.modules.lecture.model.ParticipantLectureStatistics; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryEntryRef; import org.olat.repository.manager.RepositoryEntryDAO; @@ -142,6 +144,11 @@ public class LectureServiceImpl implements LectureService { return reasonDao.getReasons(); } + @Override + public Reason getReason(Long key) { + return reasonDao.loadReason(key); + } + @Override public Reason createReason(String title, String description) { return reasonDao.createReason(title, description); @@ -282,12 +289,31 @@ public class LectureServiceImpl implements LectureService { LectureBlockImpl block = (LectureBlockImpl)lectureBlock; groupDao.removeMembership(block.getTeacherGroup(), teacher); } - + + @Override + public LectureParticipantSummary getOrCreateParticipantSummary(RepositoryEntry entry, Identity identity) { + LectureParticipantSummary summary = lectureParticipantSummaryDao.getSummary(entry, identity); + if(summary == null) { + summary = lectureParticipantSummaryDao.createSummary(entry, identity, null); + } + return summary; + } + + @Override + public LectureParticipantSummary saveParticipantSummary(LectureParticipantSummary summary) { + return lectureParticipantSummaryDao.update(summary); + } + @Override public List<LectureStatistics> getParticipantLecturesStatistics(IdentityRef identity) { return lectureBlockRollCallDao.getStatistics(identity); } + @Override + public List<ParticipantLectureStatistics> getParticipantsLecturesStatistics(RepositoryEntryRef entry) { + return lectureBlockRollCallDao.getStatistics(entry); + } + @Override public List<LectureBlockAndRollCall> getParticipantLectureBlocks(RepositoryEntryRef entry, IdentityRef participant) { return lectureBlockRollCallDao.getParticipantLectureBlockAndRollCalls(entry, participant); diff --git a/src/main/java/org/olat/modules/lecture/manager/ReasonDAO.java b/src/main/java/org/olat/modules/lecture/manager/ReasonDAO.java index 651b70a80a9..27d8d7ca27b 100644 --- a/src/main/java/org/olat/modules/lecture/manager/ReasonDAO.java +++ b/src/main/java/org/olat/modules/lecture/manager/ReasonDAO.java @@ -54,6 +54,15 @@ public class ReasonDAO { return dbInstance.getCurrentEntityManager().merge(reason); } + public Reason loadReason(Long key) { + String sb = "select reason from lecturereason reason where reason.key=:reasonKey"; + List<Reason> reasons = dbInstance.getCurrentEntityManager() + .createQuery(sb, Reason.class) + .setParameter("reasonKey", key) + .getResultList(); + return reasons == null || reasons.isEmpty() ? null : reasons.get(0); + } + public List<Reason> getReasons() { String sb = "select reason from lecturereason reason"; return dbInstance.getCurrentEntityManager() diff --git a/src/main/java/org/olat/modules/lecture/model/LectureBlockImpl.java b/src/main/java/org/olat/modules/lecture/model/LectureBlockImpl.java index 2e28492cdb9..71b3b1cb2d1 100644 --- a/src/main/java/org/olat/modules/lecture/model/LectureBlockImpl.java +++ b/src/main/java/org/olat/modules/lecture/model/LectureBlockImpl.java @@ -108,7 +108,7 @@ public class LectureBlockImpl implements Persistable, LectureBlock { @Column(name="l_roll_call_status", nullable=false, insertable=true, updatable=true) private String rollCallStatusString; - @ManyToOne(targetEntity=RepositoryEntry.class,fetch=FetchType.LAZY,optional=true) + @ManyToOne(targetEntity=ReasonImpl.class,fetch=FetchType.LAZY,optional=true) @JoinColumn(name="fk_reason", nullable=true, insertable=true, updatable=true) private Reason reasonEffectiveEnd; diff --git a/src/main/java/org/olat/modules/lecture/model/LectureParticipantSummaryImpl.java b/src/main/java/org/olat/modules/lecture/model/LectureParticipantSummaryImpl.java index 8b8c8544868..8c9fd9cd2bf 100644 --- a/src/main/java/org/olat/modules/lecture/model/LectureParticipantSummaryImpl.java +++ b/src/main/java/org/olat/modules/lecture/model/LectureParticipantSummaryImpl.java @@ -63,8 +63,8 @@ public class LectureParticipantSummaryImpl implements Persistable, LecturePartic @Column(name="lastmodified", nullable=false, insertable=true, updatable=true) private Date lastModified; - @Column(name="l_quota", nullable=true, insertable=true, updatable=true) - private Double quota; + @Column(name="l_attendance_rate", nullable=true, insertable=true, updatable=true) + private Double attendanceRate; @Column(name="l_first_admission_date", nullable=true, insertable=true, updatable=true) private Date firstAdmissionDate; @Column(name="l_attended_lectures", nullable=false, insertable=true, updatable=true) @@ -107,12 +107,12 @@ public class LectureParticipantSummaryImpl implements Persistable, LecturePartic this.lastModified = lastModified; } - public Double getQuota() { - return quota; + public Double getAttendanceRate() { + return attendanceRate; } - public void setQuota(Double quota) { - this.quota = quota; + public void setAttendanceRate(Double attendanceRate) { + this.attendanceRate = attendanceRate; } public Date getFirstAdmissionDate() { diff --git a/src/main/java/org/olat/modules/lecture/model/ParticipantLectureStatistics.java b/src/main/java/org/olat/modules/lecture/model/ParticipantLectureStatistics.java new file mode 100644 index 00000000000..e8dee444467 --- /dev/null +++ b/src/main/java/org/olat/modules/lecture/model/ParticipantLectureStatistics.java @@ -0,0 +1,78 @@ +/** + * <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.modules.lecture.model; + +/** + * + * Initial date: 7 avr. 2017<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class ParticipantLectureStatistics { + + private final Long identityKey; + + private long totalLectureBlocks; + private long totalPlannedLectures = 0l; + private long totalAttendedLectures = 0l; + private long totalAbsentLectures = 0l; + + public ParticipantLectureStatistics(Long identityKey) { + this.identityKey = identityKey; + } + + public Long getIdentityKey() { + return identityKey; + } + + public long getTotalPlannedLectures() { + return totalPlannedLectures; + } + + public void addTotalPlannedLectures(long lectures) { + totalPlannedLectures += lectures; + } + + public long getTotalAttendedLectures() { + return totalAttendedLectures; + } + + public void addTotalAttendedLectures(long lectures) { + totalAttendedLectures += lectures; + } + + public long getTotalAbsentLectures() { + return totalAbsentLectures; + } + + public void addTotalAbsentLectures(long lectures) { + totalAbsentLectures += lectures; + } + + public long getTotalLectureBlocks() { + return totalLectureBlocks; + } + + public void addTotalLectureBlocks(long lectures) { + totalLectureBlocks += lectures; + } + + +} diff --git a/src/main/java/org/olat/modules/lecture/ui/EditParticipantRateController.java b/src/main/java/org/olat/modules/lecture/ui/EditParticipantRateController.java new file mode 100644 index 00000000000..e5d304dd246 --- /dev/null +++ b/src/main/java/org/olat/modules/lecture/ui/EditParticipantRateController.java @@ -0,0 +1,121 @@ +package org.olat.modules.lecture.ui; + +import org.olat.core.gui.UserRequest; +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.FormLink; +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.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.components.link.Link; +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.id.Identity; +import org.olat.core.util.StringHelper; +import org.olat.modules.lecture.LectureParticipantSummary; +import org.olat.modules.lecture.LectureService; +import org.olat.repository.RepositoryEntry; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 6 avr. 2017<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class EditParticipantRateController extends FormBasicController { + + private TextElement rateEl; + private FormLink removeCustomRateButton; + + private double defaultRate; + private LectureParticipantSummary participantSummary; + + @Autowired + private LectureService lectureService; + + public EditParticipantRateController(UserRequest ureq, WindowControl wControl, RepositoryEntry entry, Identity identity, double defaultRate) { + super(ureq, wControl); + this.defaultRate = defaultRate; + participantSummary = lectureService.getOrCreateParticipantSummary(entry, identity); + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + long rate = Math.round(defaultRate * 100.0d); + uifactory.addStaticTextElement("entry.rate", Long.toString(rate) + "%", formLayout); + + String customRate = ""; + if(participantSummary.getAttendanceRate() != null) { + long cRate = Math.round(participantSummary.getAttendanceRate().doubleValue() * 100.0d); + customRate = Long.toString(cRate); + } + rateEl = uifactory.addTextElement("participant.rate", "participant.rate", 4, customRate, formLayout); + + FormLayoutContainer buttonsCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator()); + formLayout.add(buttonsCont); + uifactory.addFormCancelButton("cancel", buttonsCont, ureq, getWindowControl()); + removeCustomRateButton = uifactory.addFormLink("remove.custom.rate", "remove.custom.rate", null, buttonsCont, Link.BUTTON); + uifactory.addFormSubmitButton("save", buttonsCont); + } + + @Override + protected void doDispose() { + // + } + + @Override + protected boolean validateFormLogic(UserRequest ureq) { + boolean allOk = true; + + rateEl.clearError(); + if(StringHelper.containsNonWhitespace(rateEl.getValue())) { + try { + int val = Integer.parseInt(rateEl.getValue()); + if(val < 0 && val > 100) { + rateEl.setErrorKey("error.integer.between", new String[] {"0", "100"}); + } + } catch (NumberFormatException e) { + rateEl.setErrorKey("form.error.nointeger", null); + allOk &= false; + } + } + + return allOk & super.validateFormLogic(ureq); + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if(removeCustomRateButton == source) { + doRemoveCustomRate(ureq); + } + super.formInnerEvent(ureq, source, event); + } + + @Override + protected void formOK(UserRequest ureq) { + String customRate = rateEl.getValue(); + if(StringHelper.containsNonWhitespace(customRate)) { + double val = Long.parseLong(customRate) / 100.0d; + participantSummary.setAttendanceRate(val); + } else { + participantSummary.setAttendanceRate(null); + } + participantSummary = lectureService.saveParticipantSummary(participantSummary); + fireEvent(ureq, Event.DONE_EVENT); + } + + @Override + protected void formCancelled(UserRequest ureq) { + fireEvent(ureq, Event.CANCELLED_EVENT); + } + + private void doRemoveCustomRate(UserRequest ureq) { + participantSummary.setAttendanceRate(null); + participantSummary = lectureService.saveParticipantSummary(participantSummary); + fireEvent(ureq, Event.DONE_EVENT); + } +} diff --git a/src/main/java/org/olat/modules/lecture/ui/LectureRepositoryAdminController.java b/src/main/java/org/olat/modules/lecture/ui/LectureRepositoryAdminController.java index c70eece6d58..a068dbaccaa 100644 --- a/src/main/java/org/olat/modules/lecture/ui/LectureRepositoryAdminController.java +++ b/src/main/java/org/olat/modules/lecture/ui/LectureRepositoryAdminController.java @@ -26,7 +26,6 @@ import org.olat.core.gui.components.link.LinkFactory; import org.olat.core.gui.components.segmentedview.SegmentViewComponent; import org.olat.core.gui.components.segmentedview.SegmentViewEvent; import org.olat.core.gui.components.segmentedview.SegmentViewFactory; -import org.olat.core.gui.components.stack.TooledStackedPanel; import org.olat.core.gui.components.velocity.VelocityContainer; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; @@ -43,24 +42,25 @@ public class LectureRepositoryAdminController extends BasicController { private final VelocityContainer mainVC; private final SegmentViewComponent segmentView; - private final Link lecturesLink, settingsLink; - private final TooledStackedPanel toolbarPanel; + private final Link lecturesLink, settingsLink, participantsLink; private LectureListRepositoryController lecturesCtrl; private LectureRepositorySettingsController settingsCtrl; + private ParticipantListRepositoryController participantsCtrl; private RepositoryEntry entry; - public LectureRepositoryAdminController(UserRequest ureq, WindowControl wControl, TooledStackedPanel toolbarPanel, RepositoryEntry entry) { + public LectureRepositoryAdminController(UserRequest ureq, WindowControl wControl, RepositoryEntry entry) { super(ureq, wControl); this.entry = entry; - this.toolbarPanel = toolbarPanel; mainVC = createVelocityContainer("admin_repository"); segmentView = SegmentViewFactory.createSegmentView("segments", mainVC, this); - lecturesLink = LinkFactory.createLink("repo.lectures", mainVC, this); + lecturesLink = LinkFactory.createLink("repo.lectures.block", mainVC, this); segmentView.addSegment(lecturesLink, true); + participantsLink = LinkFactory.createLink("repo.participants", mainVC, this); + segmentView.addSegment(participantsLink, false); settingsLink = LinkFactory.createLink("repo.settings", mainVC, this); segmentView.addSegment(settingsLink, false); @@ -84,6 +84,8 @@ public class LectureRepositoryAdminController extends BasicController { doOpenLectures(ureq); } else if (clickedLink == settingsLink){ doOpenSettings(ureq); + } else if(clickedLink == participantsLink) { + doOpenParticipants(ureq); } } } @@ -104,4 +106,12 @@ public class LectureRepositoryAdminController extends BasicController { } mainVC.put("segmentCmp", settingsCtrl.getInitialComponent()); } + + private void doOpenParticipants(UserRequest ureq) { + if(participantsCtrl == null) { + participantsCtrl = new ParticipantListRepositoryController(ureq, getWindowControl(), entry); + listenTo(participantsCtrl); + } + mainVC.put("segmentCmp", participantsCtrl.getInitialComponent()); + } } \ No newline at end of file diff --git a/src/main/java/org/olat/modules/lecture/ui/ParticipantListDataModel.java b/src/main/java/org/olat/modules/lecture/ui/ParticipantListDataModel.java new file mode 100644 index 00000000000..05b6dc3023f --- /dev/null +++ b/src/main/java/org/olat/modules/lecture/ui/ParticipantListDataModel.java @@ -0,0 +1,100 @@ +/** + * <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.modules.lecture.ui; + +import java.util.Locale; + +import org.olat.core.commons.persistence.SortKey; +import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableDataModel; +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; + +/** + * + * Initial date: 6 avr. 2017<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class ParticipantListDataModel extends DefaultFlexiTableDataModel<ParticipantRow> +implements SortableFlexiTableDataModel<ParticipantRow> { + + private final Locale locale; + + public ParticipantListDataModel(FlexiTableColumnModel columnModel, Locale locale) { + super(columnModel); + this.locale = locale; + } + + @Override + public void sort(SortKey sortKey) { + // + } + + @Override + public Object getValueAt(int row, int col) { + ParticipantRow participant = getObject(row); + return getValueAt(participant, col); + } + + @Override + public Object getValueAt(ParticipantRow row, int col) { + if(col < ParticipantListRepositoryController.USER_PROPS_OFFSET) { + switch(ParticipantsCols.values()[col]) { + case username: return row.getIdentityName(); + case progress: return row.getStatistics(); + default: return null; + } + } + int propPos = col - ParticipantListRepositoryController.USER_PROPS_OFFSET; + return row.getIdentityProp(propPos); + } + + @Override + public DefaultFlexiTableDataModel<ParticipantRow> createCopyWithEmptyList() { + return new ParticipantListDataModel(getTableColumnModel(), locale); + } + + public enum ParticipantsCols implements FlexiSortableColumnDef { + username("table.header.username"), + progress("table.header.progress"); + + private final String i18nKey; + + private ParticipantsCols(String i18nKey) { + this.i18nKey = i18nKey; + } + + @Override + public String i18nHeaderKey() { + return i18nKey; + } + + @Override + public boolean sortable() { + return true; + } + + @Override + public String sortKey() { + return name(); + } + } +} diff --git a/src/main/java/org/olat/modules/lecture/ui/ParticipantListRepositoryController.java b/src/main/java/org/olat/modules/lecture/ui/ParticipantListRepositoryController.java new file mode 100644 index 00000000000..d88208283d9 --- /dev/null +++ b/src/main/java/org/olat/modules/lecture/ui/ParticipantListRepositoryController.java @@ -0,0 +1,232 @@ +/** + * <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.modules.lecture.ui; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.olat.basesecurity.BaseSecurity; +import org.olat.basesecurity.BaseSecurityModule; +import org.olat.basesecurity.GroupRoles; +import org.olat.core.gui.UserRequest; +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.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory; +import org.olat.core.gui.components.form.flexible.impl.elements.table.SelectionEvent; +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.gui.control.generic.closablewrapper.CloseableModalController; +import org.olat.core.id.Identity; +import org.olat.core.id.Roles; +import org.olat.modules.lecture.LectureModule; +import org.olat.modules.lecture.LectureService; +import org.olat.modules.lecture.RepositoryEntryLectureConfiguration; +import org.olat.modules.lecture.model.ParticipantLectureStatistics; +import org.olat.modules.lecture.ui.ParticipantListDataModel.ParticipantsCols; +import org.olat.modules.lecture.ui.component.LectureStatisticsCellRenderer; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.RepositoryService; +import org.olat.user.UserManager; +import org.olat.user.propertyhandlers.UserPropertyHandler; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 6 avr. 2017<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class ParticipantListRepositoryController extends FormBasicController { + + protected static final String USER_PROPS_ID = ParticipantListRepositoryController.class.getCanonicalName(); + + public static final int USER_PROPS_OFFSET = 500; + + private final boolean isAdministrativeUser; + private List<UserPropertyHandler> userPropertyHandlers; + + private FlexiTableElement tableEl; + private ParticipantListDataModel tableModel; + + private CloseableModalController cmc; + private EditParticipantRateController editRateCtrl; + + private final double defaultRate; + private final boolean rateEnabled; + private final boolean rollCallEnabled; + + private final RepositoryEntry entry; + private RepositoryEntryLectureConfiguration lectureConfig; + + @Autowired + private UserManager userManager; + @Autowired + private LectureModule lectureModule; + @Autowired + private LectureService lectureService; + @Autowired + private BaseSecurityModule securityModule; + @Autowired + private RepositoryService repositoryService; + @Autowired + private BaseSecurity securityManager; + + public ParticipantListRepositoryController(UserRequest ureq, WindowControl wControl, RepositoryEntry entry) { + super(ureq, wControl, "participant_list_overview"); + this.entry = entry; + setTranslator(userManager.getPropertyHandlerTranslator(getTranslator())); + + Roles roles = ureq.getUserSession().getRoles(); + isAdministrativeUser = securityModule.isUserAllowedAdminProps(roles); + userPropertyHandlers = userManager.getUserPropertyHandlersFor(USER_PROPS_ID, isAdministrativeUser); + + lectureConfig = lectureService.getRepositoryEntryLectureConfiguration(entry); + if(lectureConfig.isOverrideModuleDefault()) { + rateEnabled = lectureConfig.getCalculateAttendanceRate() == null ? + lectureModule.isRollCallCalculateAttendanceRateDefaultEnabled() : lectureConfig.getCalculateAttendanceRate().booleanValue(); + defaultRate = lectureConfig.getRequiredAttendanceRate() == null ? + lectureModule.getRequiredAttendanceRateDefault() : lectureConfig.getRequiredAttendanceRate().doubleValue(); + rollCallEnabled = lectureConfig.getRollCallEnabled() == null ? + lectureModule.isRollCallDefaultEnabled() : lectureConfig.getRollCallEnabled(); + } else { + rateEnabled = lectureModule.isRollCallCalculateAttendanceRateDefaultEnabled(); + defaultRate = lectureModule.getRequiredAttendanceRateDefault(); + rollCallEnabled = lectureModule.isRollCallDefaultEnabled(); + } + + initForm(ureq); + loadModel(); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel(); + if(isAdministrativeUser) { + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ParticipantsCols.username)); + } + + int colPos = USER_PROPS_OFFSET; + for (UserPropertyHandler userPropertyHandler : userPropertyHandlers) { + if (userPropertyHandler == null) continue; + + String propName = userPropertyHandler.getName(); + boolean visible = userManager.isMandatoryUserProperty(USER_PROPS_ID , userPropertyHandler); + + FlexiColumnModel col = new DefaultFlexiColumnModel(visible, userPropertyHandler.i18nColumnDescriptorLabelKey(), colPos, true, propName); + columnsModel.addFlexiColumnModel(col); + colPos++; + } + + if(rollCallEnabled) { + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ParticipantsCols.progress, new LectureStatisticsCellRenderer())); + } + if(rateEnabled) { + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel("edit", translate("edit"), "edit")); + } + + tableModel = new ParticipantListDataModel(columnsModel, getLocale()); + tableEl = uifactory.addTableElement(getWindowControl(), "table", tableModel, 20, false, getTranslator(), formLayout); + tableEl.setExportEnabled(true); + tableEl.setMultiSelect(true); + tableEl.setSelectAllEnable(true); + } + + private void loadModel() { + List<Identity> participants = repositoryService.getMembers(entry, GroupRoles.participant.name()); + List<ParticipantLectureStatistics> statistics = lectureService.getParticipantsLecturesStatistics(entry); + Map<Long, ParticipantLectureStatistics> identityToStatisticsMap = statistics.stream().collect(Collectors.toMap(s -> s.getIdentityKey(), s -> s)); + + List<ParticipantRow> rows = new ArrayList<>(participants.size()); + for(Identity participant:participants) { + ParticipantLectureStatistics stats = identityToStatisticsMap.get(participant.getKey()); + rows.add(new ParticipantRow(participant, stats, userPropertyHandlers, getLocale())); + } + tableModel.setObjects(rows); + tableEl.reset(false, false, true); + } + + @Override + protected void doDispose() { + // + } + + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + if(editRateCtrl == source) { + if(event == Event.DONE_EVENT) { + loadModel(); + } + cmc.deactivate(); + cleanUp(); + } else if (cmc == source) { + cleanUp(); + } + super.event(ureq, source, event); + } + + private void cleanUp() { + removeAsListenerAndDispose(editRateCtrl); + removeAsListenerAndDispose(cmc); + editRateCtrl = null; + cmc = null; + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if(source == tableEl) { + if(event instanceof SelectionEvent) { + SelectionEvent se = (SelectionEvent)event; + String cmd = se.getCommand(); + if("edit".equals(cmd)) { + ParticipantRow row = tableModel.getObject(se.getIndex()); + doEdit(ureq, row); + } + } + } + super.formInnerEvent(ureq, source, event); + } + + @Override + protected void formOK(UserRequest ureq) { + // + } + + private void doEdit(UserRequest ureq, ParticipantRow row) { + if(editRateCtrl != null) return; + + Identity identity = securityManager.loadIdentityByKey(row.getIdentityKey()); + editRateCtrl = new EditParticipantRateController(ureq, getWindowControl(), entry, identity, defaultRate); + listenTo(editRateCtrl); + + String title = translate("edit.participant.rate"); + cmc = new CloseableModalController(getWindowControl(), "close", editRateCtrl.getInitialComponent(), true, title, true); + listenTo(cmc); + cmc.activate(); + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/modules/lecture/ui/ParticipantRow.java b/src/main/java/org/olat/modules/lecture/ui/ParticipantRow.java new file mode 100644 index 00000000000..68fb2c6f514 --- /dev/null +++ b/src/main/java/org/olat/modules/lecture/ui/ParticipantRow.java @@ -0,0 +1,50 @@ +/** + * <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.modules.lecture.ui; + +import java.util.List; +import java.util.Locale; + +import org.olat.core.id.Identity; +import org.olat.modules.lecture.model.ParticipantLectureStatistics; +import org.olat.user.UserPropertiesRow; +import org.olat.user.propertyhandlers.UserPropertyHandler; + +/** + * Holder for the participant list + * + * + * Initial date: 6 avr. 2017<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class ParticipantRow extends UserPropertiesRow { + + private final ParticipantLectureStatistics statistics; + + public ParticipantRow(Identity identity, ParticipantLectureStatistics statistics, List<UserPropertyHandler> propertyHandlers, Locale locale) { + super(identity, propertyHandlers, locale); + this.statistics = statistics; + } + + public ParticipantLectureStatistics getStatistics() { + return statistics; + } +} diff --git a/src/main/java/org/olat/modules/lecture/ui/TeacherOverviewController.java b/src/main/java/org/olat/modules/lecture/ui/TeacherOverviewController.java index 024dceedff0..276ac3509e5 100644 --- a/src/main/java/org/olat/modules/lecture/ui/TeacherOverviewController.java +++ b/src/main/java/org/olat/modules/lecture/ui/TeacherOverviewController.java @@ -40,6 +40,7 @@ import org.olat.core.gui.components.form.flexible.impl.elements.table.TimeFlexiC import org.olat.core.gui.components.link.Link; import org.olat.core.gui.components.stack.TooledStackedPanel; 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.id.Identity; import org.olat.core.util.StringHelper; @@ -49,6 +50,7 @@ import org.olat.modules.lecture.LectureModule; import org.olat.modules.lecture.LectureService; import org.olat.modules.lecture.RepositoryEntryLectureConfiguration; import org.olat.modules.lecture.ui.TeacherOverviewDataModel.TeachCols; +import org.olat.modules.lecture.ui.component.LectureBlockStatusCellRenderer; import org.olat.repository.RepositoryEntry; import org.springframework.beans.factory.annotation.Autowired; @@ -95,7 +97,7 @@ public class TeacherOverviewController extends FormBasicController { columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TeachCols.startTime, new TimeFlexiCellRenderer(getLocale()))); columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TeachCols.endTime, new TimeFlexiCellRenderer(getLocale()))); columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TeachCols.lectureBlock)); - columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TeachCols.status)); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TeachCols.status, new LectureBlockStatusCellRenderer(getTranslator()))); columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TeachCols.details.i18nHeaderKey(), TeachCols.details.ordinal(), "details", new BooleanCellRenderer(new StaticFlexiCellRenderer(translate("table.header.details"), "details"), null))); columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TeachCols.export.i18nHeaderKey(), TeachCols.export.ordinal(), "export", @@ -108,7 +110,7 @@ public class TeacherOverviewController extends FormBasicController { private void loadModel() { List<LectureBlock> blocks = lectureService.getLectureBlocks(entry, getIdentity()); tableModel.setObjects(blocks); - tableEl.reset(true, true, true); + tableEl.reset(false, false, true); //reset startButton.setVisible(false); @@ -143,6 +145,17 @@ public class TeacherOverviewController extends FormBasicController { // } + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + if(rollCallCtrl == source) { + if(event == Event.DONE_EVENT) { + toolbarPanel.popController(rollCallCtrl); + loadModel(); + } + } + super.event(ureq, source, event); + } + @Override protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { if(source == tableEl) { @@ -167,22 +180,25 @@ public class TeacherOverviewController extends FormBasicController { } private void doSelectLectureBlock(UserRequest ureq, LectureBlock block) { + LectureBlock reloadedBlock = lectureService.getLectureBlock(block); + boolean editable = false; - if(block.getStatus().equals(LectureBlockStatus.active) - || block.getStatus().equals(LectureBlockStatus.partiallydone)) { + if(reloadedBlock.getStatus().equals(LectureBlockStatus.active) + || reloadedBlock.getStatus().equals(LectureBlockStatus.partiallydone)) { editable = true; } - List<Identity> participants = lectureService.startLectureBlock(getIdentity(), block); - rollCallCtrl = new TeacherRollCallController(ureq, getWindowControl(), block, participants, editable); + List<Identity> participants = lectureService.startLectureBlock(getIdentity(), reloadedBlock); + rollCallCtrl = new TeacherRollCallController(ureq, getWindowControl(), reloadedBlock, participants, editable); listenTo(rollCallCtrl); - toolbarPanel.pushController(block.getTitle(), rollCallCtrl); + toolbarPanel.pushController(reloadedBlock.getTitle(), rollCallCtrl); } //same as above??? private void doStartRollCall(UserRequest ureq, LectureBlock block) { - List<Identity> participants = lectureService.startLectureBlock(getIdentity(), block); - rollCallCtrl = new TeacherRollCallController(ureq, getWindowControl(), block, participants, true); + LectureBlock reloadedBlock = lectureService.getLectureBlock(block); + List<Identity> participants = lectureService.startLectureBlock(getIdentity(), reloadedBlock); + rollCallCtrl = new TeacherRollCallController(ureq, getWindowControl(), reloadedBlock, participants, true); listenTo(rollCallCtrl); - toolbarPanel.pushController(block.getTitle(), rollCallCtrl); + toolbarPanel.pushController(reloadedBlock.getTitle(), rollCallCtrl); } } diff --git a/src/main/java/org/olat/modules/lecture/ui/TeacherRollCallController.java b/src/main/java/org/olat/modules/lecture/ui/TeacherRollCallController.java index af4303e9f6f..0760cd64dfd 100644 --- a/src/main/java/org/olat/modules/lecture/ui/TeacherRollCallController.java +++ b/src/main/java/org/olat/modules/lecture/ui/TeacherRollCallController.java @@ -20,7 +20,9 @@ package org.olat.modules.lecture.ui; import java.util.ArrayList; +import java.util.Calendar; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -32,6 +34,7 @@ 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.FormLink; import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement; +import org.olat.core.gui.components.form.flexible.elements.SingleSelection; 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.FormEvent; @@ -51,8 +54,10 @@ import org.olat.core.id.Roles; import org.olat.core.util.StringHelper; import org.olat.modules.lecture.LectureBlock; import org.olat.modules.lecture.LectureBlockRollCall; +import org.olat.modules.lecture.LectureBlockStatus; import org.olat.modules.lecture.LectureModule; import org.olat.modules.lecture.LectureService; +import org.olat.modules.lecture.Reason; import org.olat.modules.lecture.ui.TeacherRollCallDataModel.RollCols; import org.olat.user.UserManager; import org.olat.user.propertyhandlers.UserPropertyHandler; @@ -76,13 +81,16 @@ public class TeacherRollCallController extends FormBasicController { private FlexiTableElement tableEl; private TeacherRollCallDataModel tableModel; + private TextElement blokcCommentEl; + private SingleSelection statusEl, effectiveEndReasonEl; + private TextElement effectiveEndHourEl, effectiveEndMinuteEl; private ReasonController reasonCtrl; private CloseableCalloutWindowController reasonCalloutCtrl; private int counter = 0; private final boolean editable; - private final LectureBlock lectureBlock; + private LectureBlock lectureBlock; private final boolean isAdministrativeUser; private final boolean autorizedAbsenceEnabled; private List<UserPropertyHandler> userPropertyHandlers; @@ -118,6 +126,94 @@ public class TeacherRollCallController extends FormBasicController { @Override protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + // form for the lecture block + FormLayoutContainer blockCont = FormLayoutContainer.createDefaultFormLayout("block", getTranslator()); + blockCont.setRootForm(mainForm); + formLayout.add("block", blockCont); + + uifactory.addStaticTextElement("lecture.title", lectureBlock.getTitle(), blockCont); + + String[] statusKeys = getAvailableStatus(); + String[] statusValues = new String[statusKeys.length]; + for(int i=statusKeys.length; i-->0; ) { + statusValues[i] = translate(statusKeys[i]); + } + statusEl = uifactory.addDropdownSingleselect("status", "lecture.block.status", blockCont, statusKeys, statusValues, null); + boolean statusFound = false; + if(lectureBlock.getStatus() != null) { + String lectureBlockStatus = lectureBlock.getStatus().name(); + for(int i=statusKeys.length; i-->0; ) { + if(lectureBlockStatus.equals(statusKeys[i])) { + statusEl.select(statusKeys[i], true); + statusFound = true; + break; + } + } + } + if(!statusFound) { + statusEl.select(statusKeys[0], true); + } + + String datePage = velocity_root + "/date_start_end.html"; + FormLayoutContainer dateCont = FormLayoutContainer.createCustomFormLayout("start_end", getTranslator(), datePage); + dateCont.setLabel("lecture.block.effective.end", null); + blockCont.add(dateCont); + + effectiveEndHourEl = uifactory.addTextElement("lecture.end.hour", null, 2, "", dateCont); + effectiveEndHourEl.setDomReplacementWrapperRequired(false); + effectiveEndHourEl.setDisplaySize(2); + effectiveEndHourEl.setEnabled(editable); + effectiveEndMinuteEl = uifactory.addTextElement("lecture.end.minute", null, 2, "", dateCont); + effectiveEndMinuteEl.setDomReplacementWrapperRequired(false); + effectiveEndMinuteEl.setDisplaySize(2); + effectiveEndMinuteEl.setEnabled(editable); + if(lectureBlock != null) { + Calendar cal = Calendar.getInstance(); + if(lectureBlock.getEffectiveEndDate() != null) { + cal.setTime(lectureBlock.getEffectiveEndDate()); + } else if(lectureBlock.getEndDate() != null) { + cal.setTime(lectureBlock.getEndDate()); + } + int hour = cal.get(Calendar.HOUR_OF_DAY); + int minute = cal.get(Calendar.MINUTE); + effectiveEndHourEl.setValue(Integer.toString(hour)); + effectiveEndMinuteEl.setValue(Integer.toString(minute)); + } + + List<String> reasonKeyList = new ArrayList<>(); + List<String> reasonValueList = new ArrayList<>(); + reasonKeyList.add("-"); + reasonValueList.add("-"); + + List<Reason> allReasons = lectureService.getAllReasons(); + for(Reason reason:allReasons) { + reasonKeyList.add(reason.getKey().toString()); + reasonValueList.add(reason.getTitle()); + } + effectiveEndReasonEl = uifactory.addDropdownSingleselect("effective.reason", "lecture.block.effective.reason", blockCont, + reasonKeyList.toArray(new String[reasonKeyList.size()]), reasonValueList.toArray(new String[reasonValueList.size()]), null); + effectiveEndReasonEl.setEnabled(editable); + boolean found = false; + if(lectureBlock.getReasonEffectiveEnd() != null) { + String selectedReasonKey = lectureBlock.getReasonEffectiveEnd().getKey().toString(); + for(String reasonKey:reasonKeyList) { + if(reasonKey.equals(selectedReasonKey)) { + effectiveEndReasonEl.select(reasonKey, true); + found = true; + break; + } + } + } + if(!found) { + effectiveEndReasonEl.select(reasonKeyList.get(0), true); + } + + String blockComment = lectureBlock.getComment(); + blokcCommentEl = uifactory.addTextElement("block.comment", "lecture.block.comment", 256, blockComment, blockCont); + blokcCommentEl.setEnabled(editable); + + // table + FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel(); if(isAdministrativeUser) { columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(RollCols.username)); @@ -154,6 +250,19 @@ public class TeacherRollCallController extends FormBasicController { uifactory.addFormSubmitButton("save", "save", formLayout); } + private String[] getAvailableStatus() { + List<String> statusList = new ArrayList<>(); + statusList.add(LectureBlockStatus.active.name()); + if(lectureModule.isStatusPartiallyDoneEnabled()) { + statusList.add(LectureBlockStatus.partiallydone.name()); + } + statusList.add(LectureBlockStatus.done.name()); + if(lectureModule.isStatusCancelledEnabled()) { + statusList.add(LectureBlockStatus.cancelled.name()); + } + return statusList.toArray(new String[statusList.size()]); + } + private void loadModel() { List<LectureBlockRollCall> rollCalls = lectureService.getRollCalls(lectureBlock); Map<Identity,LectureBlockRollCall> rollCallMap = new HashMap<>(); @@ -230,6 +339,7 @@ public class TeacherRollCallController extends FormBasicController { String commentId = "comment_".concat(Integer.toString(++counter)); TextElement commentEl = uifactory.addTextElement(commentId, commentId, null, 128, comment, flc); commentEl.setDomReplacementWrapperRequired(false); + commentEl.setEnabled(editable); row.setCommentEl(commentEl); flc.add(commentEl); return row; @@ -302,21 +412,73 @@ public class TeacherRollCallController extends FormBasicController { protected boolean validateFormLogic(UserRequest ureq) { boolean allOk = true; - for(int i=tableModel.getRowCount(); i-->0; ) { - TeacherRollCallRow row = tableModel.getObject(i); - if(row.getRollCall() == null) { - //??? stop? - } else { - String reason = row.getRollCall().getAbsenceReason(); - if(row.getAuthorizedAbsence().isAtLeastSelected(1) && !StringHelper.containsNonWhitespace(reason)) { - row.getAuthorizedAbsence().setErrorKey("error.reason.mandatory", null); - allOk &= false; + + boolean fullValidation = false; + if(!statusEl.isOneSelected()) { + statusEl.setErrorKey("form.legende.mandatory", null); + allOk &= false; + } else { + fullValidation = LectureBlockStatus.done.name().equals(statusEl.getSelectedKey()); + } + + //block form + if(StringHelper.containsNonWhitespace(effectiveEndHourEl.getValue()) + || StringHelper.containsNonWhitespace(effectiveEndMinuteEl.getValue())) { + allOk &= validateInt(effectiveEndHourEl, 24, fullValidation); + allOk &= validateInt(effectiveEndMinuteEl, 60, fullValidation); + + if(fullValidation && (!effectiveEndReasonEl.isOneSelected() || effectiveEndReasonEl.isSelected(0))) { + effectiveEndReasonEl.setErrorKey("error.reason.mandatory", null); + allOk &= false; + } + } else if(fullValidation) { + effectiveEndHourEl.setErrorKey("form.legende.mandatory", null); + allOk &= false; + } + + // table + if(fullValidation) { + for(int i=tableModel.getRowCount(); i-->0; ) { + TeacherRollCallRow row = tableModel.getObject(i); + row.getAuthorizedAbsence().clearError(); + + if(row.getRollCall() == null) { + //??? stop? + } else { + String reason = row.getRollCall().getAbsenceReason(); + if(row.getAuthorizedAbsence().isAtLeastSelected(1) && !StringHelper.containsNonWhitespace(reason)) { + row.getAuthorizedAbsence().setErrorKey("error.reason.mandatory", null); + allOk &= false; + } } } } return allOk & super.validateFormLogic(ureq); } + + private boolean validateInt(TextElement element, int max, boolean mandatory) { + boolean allOk = true; + + element.clearError(); + if(StringHelper.containsNonWhitespace(element.getValue())) { + try { + int val = Integer.parseInt(element.getValue()); + if(val < 0 || val > max) { + element.setErrorKey("error.integer.between", new String[] { "0", Integer.toString(max)} ); + allOk &= false; + } + } catch (NumberFormatException e) { + element.setErrorKey("error.integer.between", new String[] { "0", Integer.toString(max)} ); + allOk &= false; + } + } else if(mandatory) { + element.setErrorKey("form.legende.mandatory", null); + allOk &= false; + } + + return allOk; + } @Override protected void formOK(UserRequest ureq) { @@ -337,6 +499,35 @@ public class TeacherRollCallController extends FormBasicController { comment, absences); row.setRollCall(rollCall); } + + lectureBlock = lectureService.getLectureBlock(lectureBlock); + lectureBlock.setComment(blokcCommentEl.getValue()); + lectureBlock.setStatus(LectureBlockStatus.valueOf(statusEl.getSelectedKey())); + Date effectiveEndDate = getEffectiveEndDate(); + if(effectiveEndDate == null) { + lectureBlock.setReasonEffectiveEnd(null); + } else { + lectureBlock.setEffectiveEndDate(effectiveEndDate); + Long reasonKey = new Long(effectiveEndReasonEl.getSelectedKey()); + Reason selectedReason = lectureService.getReason(reasonKey); + lectureBlock.setReasonEffectiveEnd(selectedReason); + } + + lectureBlock = lectureService.save(lectureBlock, null); + fireEvent(ureq, Event.DONE_EVENT); + } + + private Date getEffectiveEndDate() { + Date effectiveEndDate = null; + if(StringHelper.containsNonWhitespace(effectiveEndHourEl.getValue()) + && StringHelper.containsNonWhitespace(effectiveEndMinuteEl.getValue())) { + Calendar cal = Calendar.getInstance(); + cal.setTime(lectureBlock.getStartDate()); + cal.set(Calendar.HOUR_OF_DAY, Integer.parseInt(effectiveEndHourEl.getValue())); + cal.set(Calendar.MINUTE, Integer.parseInt(effectiveEndMinuteEl.getValue())); + effectiveEndDate = cal.getTime(); + } + return effectiveEndDate; } private void doCheckAllRow(TeacherRollCallRow row) { @@ -376,6 +567,7 @@ public class TeacherRollCallController extends FormBasicController { } row.getReasonLink().setVisible(authorized); row.getAuthorizedAbsenceCont().setDirty(true); + row.getAuthorizedAbsence().clearError(); row.setRollCall(rollCall); } diff --git a/src/main/java/org/olat/modules/lecture/ui/_content/authorized_absence_cell.html b/src/main/java/org/olat/modules/lecture/ui/_content/authorized_absence_cell.html index b3af0b72bf6..ab10fa851fd 100644 --- a/src/main/java/org/olat/modules/lecture/ui/_content/authorized_absence_cell.html +++ b/src/main/java/org/olat/modules/lecture/ui/_content/authorized_absence_cell.html @@ -1,6 +1,6 @@ -<div id="$r.getCId()" class="o_lecture_authorized_absence"> -$r.render($row.getAuthorizedAbsence()) $r.render($row.getReasonLink()) -</div> -#if($f.hasError($row.getAuthorizedAbsence().getComponent().getComponentName())) - <div>$r.render("${row.getAuthorizedAbsence().getComponent().getComponentName()}_ERROR")</div> -#end \ No newline at end of file +<div id="$r.getCId()"> + <div class="o_lecture_authorized_absence">$r.render($row.getAuthorizedAbsence()) $r.render($row.getReasonLink())</div> + #if($f.hasError($row.getAuthorizedAbsence().getComponent().getComponentName())) + <div>$r.render("${row.getAuthorizedAbsence().getComponent().getComponentName()}_ERROR")</div> + #end +</div> \ No newline at end of file diff --git a/src/main/java/org/olat/modules/lecture/ui/_content/date_start_end.html b/src/main/java/org/olat/modules/lecture/ui/_content/date_start_end.html index 52dc9851c71..f154680ba12 100644 --- a/src/main/java/org/olat/modules/lecture/ui/_content/date_start_end.html +++ b/src/main/java/org/olat/modules/lecture/ui/_content/date_start_end.html @@ -1,2 +1,7 @@ <div class="o_date_ms form-inline">$r.render("lecture.end.hour"):$r.render("lecture.end.minute")</div> - +#if($f.hasError("lecture.end.hour")) + <div>$r.render("lecture.end.hour_ERROR")</div> +#end +#if($f.hasError("lecture.end.minute")) + <div>$r.render("lecture.end.minute_ERROR")</div> +#end \ No newline at end of file diff --git a/src/main/java/org/olat/modules/lecture/ui/_content/participant_list_overview.html b/src/main/java/org/olat/modules/lecture/ui/_content/participant_list_overview.html new file mode 100644 index 00000000000..bade9402acd --- /dev/null +++ b/src/main/java/org/olat/modules/lecture/ui/_content/participant_list_overview.html @@ -0,0 +1 @@ +$r.render("table") \ No newline at end of file diff --git a/src/main/java/org/olat/modules/lecture/ui/_content/rollcall.html b/src/main/java/org/olat/modules/lecture/ui/_content/rollcall.html index f7103ee974e..61b75caefa3 100644 --- a/src/main/java/org/olat/modules/lecture/ui/_content/rollcall.html +++ b/src/main/java/org/olat/modules/lecture/ui/_content/rollcall.html @@ -1,3 +1,4 @@ +$r.render("block") $r.render("table") <div class="o_button_group"> $r.render("save") diff --git a/src/main/java/org/olat/modules/lecture/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/lecture/ui/_i18n/LocalStrings_de.properties index f6b01dadc2d..755227bc6c5 100644 --- a/src/main/java/org/olat/modules/lecture/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/modules/lecture/ui/_i18n/LocalStrings_de.properties @@ -1,48 +1,76 @@ -#Thu Sep 03 11:09:03 CEST 2015 +#Thu Apr 06 19:05:04 CEST 2017 +active=Aktiv add.lecture=Neue Lektion add.reason=Begr\u00FCndung erstellen admin.menu.title=Lektionen admin.menu.title.alt=Lektionen und Absenzmanagement all=Alle appeal=Rekurs -appeal.title=Rekurs "{0}" -appeal.subject=Rekurs Lektionenblock "{0}" appeal.contact.list=Lehrer +appeal.subject=Rekurs Lektionenblock "{0}" +appeal.title=Rekurs "{0}" +cancelled=Abgesagt +config.calculate.attendance.rate=Presenz quota berechnen +config.override=Standard Konfiguration \u00FCberschreiben +config.override.no=Nein +config.override.yes=Ja +config.rollcall.enabled=Roll call einschalten +config.sync.participant.calendar=Teilnehmer Kalender synchronizieren +config.sync.teacher.calendar=Lehrer Kalender synchronizieren +done=Erledigt +edit.participant.rate=Teilnehmersschwellwert bearbeiten edit.reason=Begr\u00FCndung bearbeiten +entry.rate=Kursschwellwert error.integer.between=Der Eingabe muss ein Zahl zwischen {0} und {1} error.reason.mandatory=Begr\u00FCndung ist erforderlich lecture.admin.enabled=Lektionen und absenzmanagement einschalten lecture.admin.title=Konfiguration lektionen und absenzmanagement -menu.my.lectures=Absenzen -menu.my.lectures.alt=Lektionen und Absenzen -repo.settings=Konfiguration -config.override=Standard Konfiguration \u00FCberschreiben -config.override.yes=Ja -config.override.no=Nein -config.rollcall.enabled=Roll call einschalten -config.calculate.attendance.rate=Presenz quota berechnen -config.sync.teacher.calendar=Lehrer Kalender synchronizieren -config.sync.participant.calendar=Teilnehmer Kalender synchronizieren -repo.lectures=Lektionen -lecture.title=Titel +lecture.attendance.rate.default=Absenzenquote global in % +lecture.authorized.absence.enabled=Entschuldigte Absenzen +lecture.block.comment=Bemerkung +lecture.block.effective.end=Effektives Ende +lecture.block.effective.reason=Begr\u00FCndung +lecture.block.status=Status +lecture.date=Datum lecture.descr=Beschreibung -lecture.preparation=Vorbereitung +lecture.end=End lecture.groups=Studenten lecture.location=Ort -lecture.date=Datum +lecture.preparation=Vorbereitung lecture.start=Beginn -lecture.end=End lecture.teacher=Lehrer -lectures.admin.settings=Konfiguration +lecture.title=Titel lectures.admin.reasons=Begr\u00FCndung -lecture.attendance.rate.default=Absenzenquote global in % -lecture.authorized.absence.enabled=Entschuldigte Absenzen +lectures.admin.settings=Konfiguration +menu.my.lectures=Absenzen +menu.my.lectures.alt=Lektionen und Absenzen +partiallydone=Teilweise erledigt +participant.rate=Schwellwert planned.lectures=Geplante Lektionen reason=Begr\u00FCndung +reason.description=Beschreibung reason.id=ID reason.title=Bezeichnung -reason.description=Beschreibung +remove.custom.rate=Pers\u00F6nliches Schwellwert entfernen +repo.lectures=Lektionen +repo.lectures.block=Lektionenbl\u00F6cke +repo.participants=Teilnehmer +repo.settings=Konfiguration +table.header.absence.reason=L +table.header.authorized.absence=Entschuldigt +table.header.coach=Dozent +table.header.comment=Kommentar +table.header.date=Datum +table.header.details=Details +table.header.end.time=Bis +table.header.entry=Kurs +table.header.export=Export table.header.lecture.1=1 +table.header.lecture.10=10 +table.header.lecture.11=11 +table.header.lecture.12=12 +table.header.lecture.13=13 +table.header.lecture.14=14 table.header.lecture.2=2 table.header.lecture.3=3 table.header.lecture.4=4 @@ -51,24 +79,10 @@ table.header.lecture.6=6 table.header.lecture.7=7 table.header.lecture.8=8 table.header.lecture.9=9 -table.header.lecture.10=10 -table.header.lecture.11=11 -table.header.lecture.12=12 -table.header.lecture.13=13 -table.header.lecture.14=14 -table.header.comment=Kommentar -table.header.username=Benutzername -table.header.authorized.absence=Entschuldigt -table.header.absence.reason=L -table.header.entry=Kurs -table.header.quota=Quota -table.header.progress=Progress -table.header.date=Datum table.header.lecture.block=Lektionenblock -table.header.coach=Dozent table.header.present=Anwesend +table.header.progress=Progress +table.header.quota=Quota table.header.start.time=Von -table.header.end.time=Bis table.header.status=Status -table.header.details=Details -table.header.export=Export \ No newline at end of file +table.header.username=Benutzername diff --git a/src/main/java/org/olat/modules/lecture/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/modules/lecture/ui/_i18n/LocalStrings_en.properties index 6956aca0a5e..02371d0cabf 100644 --- a/src/main/java/org/olat/modules/lecture/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/modules/lecture/ui/_i18n/LocalStrings_en.properties @@ -1,47 +1,75 @@ -#Thu Sep 03 11:09:03 CEST 2015 +#Thu Apr 06 19:04:46 CEST 2017 +active=Active add.lecture=New lecture add.reason=Add reason admin.menu.title=Lectures admin.menu.title.alt=Lectures and absence management all=All appeal=Appeal -appeal.title=Appeal "{0}" -appeal.subject=Appeal lecture block "{0}" appeal.contact.list=Teacher -edit.reason=Edit reason -error.reason.mandatory=Reason is mandatory -lecture.admin.enabled=Enable lectures and absence management -lecture.admin.title=Configuration lectures and absence management -menu.my.lectures=Absences -menu.my.lectures.alt=Lectures and absences -repo.lectures=Lectures -repo.settings=Configuration +appeal.subject=Appeal lecture block "{0}" +appeal.title=Appeal "{0}" +cancelled=Cancelled +config.calculate.attendance.rate=Calculate attendance rate config.override=Override default configuration -config.override.yes=Yes config.override.no=No +config.override.yes=Yes config.rollcall.enabled=Roll call enabled -config.calculate.attendance.rate=Calculate attendance rate -config.sync.teacher.calendar=Synchronize teacher calendar config.sync.participant.calendar=Synchronize participants calendar -lecture.title=Title +config.sync.teacher.calendar=Synchronize teacher calendar +done=Done +edit.participant.rate=Edit participant's rate +edit.reason=Edit reason +entry.rate=Course's rate +error.reason.mandatory=Reason is mandatory +lecture.admin.enabled=Enable lectures and absence management +lecture.admin.title=Configuration lectures and absence management +lecture.attendance.rate.default=Absence quota global in % +lecture.authorized.absence.enabled=Authorized absences +lecture.block.comment=Comment +lecture.block.effective.end=Effective end +lecture.block.effective.reason=Reason +lecture.block.status=Status +lecture.date=Date lecture.descr=Description -lecture.preparation=Preparation +lecture.end=End lecture.groups=Students lecture.location=Location -lecture.date=Date +lecture.preparation=Preparation lecture.start=Begin -lecture.end=End lecture.teacher=Teacher -lectures.admin.settings=Settings +lecture.title=Title lectures.admin.reasons=Reasons -lecture.attendance.rate.default=Absence quota global in % -lecture.authorized.absence.enabled=Authorized absences +lectures.admin.settings=Settings +menu.my.lectures=Absences +menu.my.lectures.alt=Lectures and absences +partiallydone=Partially done +participant.rate=Rate planned.lectures=Planned lectures reason=Reason +reason.description=Description reason.id=ID reason.title=Title -reason.description=Description +remove.custom.rate=Remove custom rate +repo.lectures=Lectures +repo.lectures.block=Lectures blocs +repo.participants=Participants +repo.settings=Configuration +table.header.absence.reason=L +table.header.authorized.absence=Excused +table.header.coach=Coach +table.header.comment=Comment +table.header.date=Date +table.header.details=Details +table.header.end.time=To +table.header.entry=Course +table.header.export=Export table.header.lecture.1=1 +table.header.lecture.10=10 +table.header.lecture.11=11 +table.header.lecture.12=12 +table.header.lecture.13=13 +table.header.lecture.14=14 table.header.lecture.2=2 table.header.lecture.3=3 table.header.lecture.4=4 @@ -50,24 +78,10 @@ table.header.lecture.6=6 table.header.lecture.7=7 table.header.lecture.8=8 table.header.lecture.9=9 -table.header.lecture.10=10 -table.header.lecture.11=11 -table.header.lecture.12=12 -table.header.lecture.13=13 -table.header.lecture.14=14 -table.header.comment=Comment -table.header.username=Username -table.header.authorized.absence=Excused -table.header.absence.reason=L -table.header.entry=Course -table.header.quota=Quota -table.header.progress=Progress -table.header.date=Date table.header.lecture.block=Lecture block -table.header.coach=Coach table.header.present=Present +table.header.progress=Progress +table.header.quota=Quota table.header.start.time=From -table.header.end.time=To table.header.status=Status -table.header.details=Details -table.header.export=Export \ No newline at end of file +table.header.username=Username diff --git a/src/main/java/org/olat/modules/lecture/ui/component/LectureBlockStatusCellRenderer.java b/src/main/java/org/olat/modules/lecture/ui/component/LectureBlockStatusCellRenderer.java new file mode 100644 index 00000000000..12284766a9b --- /dev/null +++ b/src/main/java/org/olat/modules/lecture/ui/component/LectureBlockStatusCellRenderer.java @@ -0,0 +1,51 @@ +/** + * <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.modules.lecture.ui.component; + +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiCellRenderer; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableComponent; +import org.olat.core.gui.render.Renderer; +import org.olat.core.gui.render.StringOutput; +import org.olat.core.gui.render.URLBuilder; +import org.olat.core.gui.translator.Translator; +import org.olat.modules.lecture.LectureBlockStatus; + +/** + * + * Initial date: 6 avr. 2017<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class LectureBlockStatusCellRenderer implements FlexiCellRenderer { + + private final Translator translator; + + public LectureBlockStatusCellRenderer(Translator translator) { + this.translator = translator; + } + + @Override + public void render(Renderer renderer, StringOutput target, Object cellValue, int row, FlexiTableComponent source, URLBuilder ubu, Translator trans) { + if(cellValue instanceof LectureBlockStatus) { + LectureBlockStatus status = (LectureBlockStatus)cellValue; + target.append(translator.translate(status.name())); + } + } +} diff --git a/src/main/java/org/olat/modules/lecture/ui/component/LectureStatisticsCellRenderer.java b/src/main/java/org/olat/modules/lecture/ui/component/LectureStatisticsCellRenderer.java index 606b76e19ec..5ece306c060 100644 --- a/src/main/java/org/olat/modules/lecture/ui/component/LectureStatisticsCellRenderer.java +++ b/src/main/java/org/olat/modules/lecture/ui/component/LectureStatisticsCellRenderer.java @@ -26,6 +26,7 @@ import org.olat.core.gui.render.StringOutput; import org.olat.core.gui.render.URLBuilder; import org.olat.core.gui.translator.Translator; import org.olat.modules.lecture.model.LectureStatistics; +import org.olat.modules.lecture.model.ParticipantLectureStatistics; /** * @@ -35,30 +36,37 @@ public class LectureStatisticsCellRenderer implements FlexiCellRenderer { @Override public void render(Renderer renderer, StringOutput target, Object cellValue, int row, FlexiTableComponent source, URLBuilder ubu, Translator translator) { - if(cellValue instanceof LectureStatistics) { LectureStatistics stats = (LectureStatistics)cellValue; long total = stats.getTotalPlannedLectures(); long attended = stats.getTotalAttendedLectures(); - long attendedPercent = (attended == 0) ? 0 : Math.round(100.0f * ((double)attended / (double)total)); long absent = stats.getTotalAbsentLectures(); - long absentPercent = (absent == 0) ? 0 : Math.round(100.0f * ((double)absent / (double)total)); - - target.append("<div class='progress'>"); - //attended - target.append("<div class='progress-bar o_lecture_attended' role='progressbar' aria-valuenow='").append(attended) - .append("' aria-valuemin='0' aria-valuemax='").append(total) - .append("' style='width: ").append(attendedPercent).append("%;'>") - .append("<span class='sr-only'>").append(attendedPercent).append("%</span></div>"); - //absent - target.append("<div class='progress-bar o_lecture_absent' role='progressbar' aria-valuenow='").append(absent) - .append("' aria-valuemin='0' aria-valuemax='").append(total) - .append("' style='width: ").append(absentPercent).append("%;'>") - .append("<span class='sr-only'>").append(absentPercent).append("%</span></div>"); - - - target.append("</div>"); - + render(target, total, attended, absent); + } else if(cellValue instanceof ParticipantLectureStatistics) { + ParticipantLectureStatistics stats = (ParticipantLectureStatistics)cellValue; + long total = stats.getTotalPlannedLectures(); + long attended = stats.getTotalAttendedLectures(); + long absent = stats.getTotalAbsentLectures(); + render(target, total, attended, absent); } } + + private void render(StringOutput target, long total, long attended, long absent) { + long attendedPercent = (attended == 0) ? 0 : Math.round(100.0f * ((double)attended / (double)total)); + long absentPercent = (absent == 0) ? 0 : Math.round(100.0f * ((double)absent / (double)total)); + target.append("<div class='progress'>"); + //attended + target.append("<div class='progress-bar o_lecture_attended' role='progressbar' aria-valuenow='").append(attended) + .append("' aria-valuemin='0' aria-valuemax='").append(total) + .append("' style='width: ").append(attendedPercent).append("%;'>") + .append("<span class='sr-only'>").append(attendedPercent).append("%</span></div>"); + //absent + target.append("<div class='progress-bar o_lecture_absent' role='progressbar' aria-valuenow='").append(absent) + .append("' aria-valuemin='0' aria-valuemax='").append(total) + .append("' style='width: ").append(absentPercent).append("%;'>") + .append("<span class='sr-only'>").append(absentPercent).append("%</span></div>"); + + + target.append("</div>"); + } } diff --git a/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml b/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml index f678b0bce6d..b21b2155678 100644 --- a/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml +++ b/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml @@ -358,6 +358,29 @@ </bean> </entry> + <entry key="org.olat.modules.lecture.ui.ParticipantListRepositoryController"> + <bean class="org.olat.user.propertyhandlers.UserPropertyUsageContext"> + <property name="description" value="List of users in lecture blocks overview" /> + <property name="propertyHandlers"> + <list> + <ref bean="userPropertyFirstName" /> + <ref bean="userPropertyLastName" /> + <ref bean="userPropertyEmail" /> + </list> + </property> + <property name="adminViewOnlyProperties"> + <set></set> + </property> + <property name="mandatoryProperties"> + <set> + <ref bean="userPropertyFirstName" /> + <ref bean="userPropertyLastName" /> + <ref bean="userPropertyEmail" /> + </set> + </property> + </bean> + </entry> + <entry key="org.olat.group.ui.main.MemberInfoController"> <!-- First name and last name are already shown --> <bean class="org.olat.user.propertyhandlers.UserPropertyUsageContext"> diff --git a/src/main/resources/database/mysql/alter_11_4_x_to_11_5_0.sql b/src/main/resources/database/mysql/alter_11_4_x_to_11_5_0.sql index dfb6ce86017..37b4ba3daba 100644 --- a/src/main/resources/database/mysql/alter_11_4_x_to_11_5_0.sql +++ b/src/main/resources/database/mysql/alter_11_4_x_to_11_5_0.sql @@ -79,7 +79,7 @@ create table o_lecture_participant_summary ( id bigint not null auto_increment, creationdate datetime not null, lastmodified datetime not null, - l_quota float(65,30) default null, + l_attendance_rate float(65,30) default null, l_first_admission_date datetime default null, l_attended_lectures bigint not null default 0, l_absent_lectures bigint not null default 0, diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql index ec353d4e2f6..7071c2c96dd 100644 --- a/src/main/resources/database/mysql/setupDatabase.sql +++ b/src/main/resources/database/mysql/setupDatabase.sql @@ -1919,6 +1919,99 @@ create table o_sms_message_log ( primary key (id) ); +-- lectures +create table o_lecture_reason ( + id bigint not null auto_increment, + creationdate datetime not null, + lastmodified datetime not null, + l_title varchar(255), + l_descr varchar(2000), + primary key (id) +); + +create table o_lecture_block ( + id bigint not null auto_increment, + creationdate datetime not null, + lastmodified datetime not null, + l_external_id varchar(255), + l_title varchar(255), + l_descr mediumtext, + l_preparation mediumtext, + l_location varchar(255), + l_comment mediumtext, + l_log mediumtext, + l_start_date datetime not null, + l_end_date datetime not null, + l_eff_end_date datetime, + l_planned_lectures_num bigint not null default 0, + l_effective_lectures_num bigint not null default 0, + l_effective_lectures varchar(128), + l_status varchar(16) not null, + l_roll_call_status varchar(16) not null, + fk_reason bigint, + fk_entry bigint not null, + fk_teacher_group bigint not null, + primary key (id) +); + +create table o_lecture_block_to_group ( + id bigint not null auto_increment, + fk_lecture_block bigint not null, + fk_group bigint not null, + primary key (id) +); + +create table o_lecture_block_roll_call ( + id bigint not null auto_increment, + creationdate datetime not null, + lastmodified datetime not null, + l_comment mediumtext, + l_log mediumtext, + l_lectures_attended varchar(128), + l_lectures_absent varchar(128), + l_lectures_attended_num bigint not null default 0, + l_lectures_absent_num bigint not null default 0, + l_absence_reason mediumtext, + l_absence_authorized bit default null, + l_absence_appeal_date datetime, + fk_lecture_block bigint not null, + fk_identity bigint not null, + primary key (id) +); + +create table o_lecture_participant_summary ( + id bigint not null auto_increment, + creationdate datetime not null, + lastmodified datetime not null, + l_attendance_rate float(65,30) default null, + l_first_admission_date datetime default null, + l_attended_lectures bigint not null default 0, + l_absent_lectures bigint not null default 0, + l_excused_lectures bigint not null default 0, + l_planneds_lectures bigint not null default 0, + fk_entry bigint not null, + fk_identity bigint not null, + primary key (id), + unique (fk_entry, fk_identity) +); + +create table o_lecture_entry_config ( + id bigint not null auto_increment, + creationdate datetime not null, + lastmodified datetime not null, + l_lecture_enabled bool default null, + l_override_module_def bool default false, + l_rollcall_enabled bool default null, + l_calculate_attendance_rate bool default null, + l_required_attendance_rate float(65,30) default null, + l_sync_calendar_teacher bool default null, + l_sync_calendar_participant bool default null, + fk_entry bigint not null, + unique(fk_entry), + primary key (id) +); + + -- user view create view o_bs_identity_short_v as ( select @@ -2250,6 +2343,12 @@ alter table o_pf_binder_user_infos ENGINE = InnoDB; alter table o_eva_form_session ENGINE = InnoDB; alter table o_eva_form_response ENGINE = InnoDB; alter table o_sms_message_log ENGINE = InnoDB; +alter table o_lecture_reason ENGINE = InnoDB; +alter table o_lecture_block ENGINE = InnoDB; +alter table o_lecture_block_to_group ENGINE = InnoDB; +alter table o_lecture_block_roll_call ENGINE = InnoDB; +alter table o_lecture_participant_summary ENGINE = InnoDB; +alter table o_lecture_entry_config ENGINE = InnoDB; -- rating alter table o_userrating add constraint FKF26C8375236F20X foreign key (creator_id) references o_bs_identity (id); @@ -2709,6 +2808,22 @@ create index cer_uuid_idx on o_cer_certificate (c_uuid); -- sms alter table o_sms_message_log add constraint sms_log_to_identity_idx foreign key (fk_identity) references o_bs_identity (id); +-- lecture +alter table o_lecture_block add constraint lec_block_entry_idx foreign key (fk_entry) references o_repositoryentry (repositoryentry_id); +alter table o_lecture_block add constraint lec_block_gcoach_idx foreign key (fk_teacher_group) references o_bs_group (id); +alter table o_lecture_block add constraint lec_block_reason_idx foreign key (fk_reason) references o_lecture_reason (id); + +alter table o_lecture_block_to_group add constraint lec_block_to_block_idx foreign key (fk_group) references o_bs_group (id); +alter table o_lecture_block_to_group add constraint lec_block_to_group_idx foreign key (fk_lecture_block) references o_lecture_block (id); + +alter table o_lecture_block_roll_call add constraint lec_call_block_idx foreign key (fk_lecture_block) references o_lecture_block (id); +alter table o_lecture_block_roll_call add constraint lec_call_identity_idx foreign key (fk_identity) references o_bs_identity (id); + +alter table o_lecture_participant_summary add constraint lec_part_entry_idx foreign key (fk_entry) references o_repositoryentry (repositoryentry_id); +alter table o_lecture_participant_summary add constraint lec_part_ident_idx foreign key (fk_identity) references o_bs_identity (id); + +alter table o_lecture_entry_config add constraint lec_entry_config_entry_idx foreign key (fk_entry) references o_repositoryentry (repositoryentry_id); + -- o_logging_table create index log_target_resid_idx on o_loggingtable(targetresid); create index log_ptarget_resid_idx on o_loggingtable(parentresid); diff --git a/src/main/resources/database/postgresql/alter_11_4_x_to_11_5_0.sql b/src/main/resources/database/postgresql/alter_11_4_x_to_11_5_0.sql index 186bd0814d6..697f8f1b329 100644 --- a/src/main/resources/database/postgresql/alter_11_4_x_to_11_5_0.sql +++ b/src/main/resources/database/postgresql/alter_11_4_x_to_11_5_0.sql @@ -82,7 +82,7 @@ create table o_lecture_participant_summary ( id bigserial not null, creationdate timestamp not null, lastmodified timestamp not null, - l_quota float(24) default null, + l_attendance_rate float(24) default null, l_first_admission_date timestamp default null, l_attended_lectures int8 not null default 0, l_absent_lectures int8 not null default 0, diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql index 0910aee819b..59232fdbbcc 100644 --- a/src/main/resources/database/postgresql/setupDatabase.sql +++ b/src/main/resources/database/postgresql/setupDatabase.sql @@ -1916,6 +1916,99 @@ create table o_sms_message_log ( primary key (id) ); +-- lectures +create table o_lecture_reason ( + id bigserial not null, + creationdate timestamp not null, + lastmodified timestamp not null, + l_title varchar(255), + l_descr varchar(2000), + primary key (id) +); + + +create table o_lecture_block ( + id bigserial not null, + creationdate timestamp not null, + lastmodified timestamp not null, + l_external_id varchar(255), + l_title varchar(255), + l_descr text, + l_preparation text, + l_location varchar(255), + l_comment text, + l_log text, + l_start_date timestamp not null, + l_end_date timestamp not null, + l_eff_end_date timestamp, + l_planned_lectures_num int8 not null default 0, + l_effective_lectures_num int8 not null default 0, + l_effective_lectures varchar(128), + l_status varchar(16) not null, + l_roll_call_status varchar(16) not null, + fk_reason int8, + fk_entry int8 not null, + fk_teacher_group int8 not null, + primary key (id) +); + +create table o_lecture_block_to_group ( + id bigserial not null, + fk_lecture_block int8 not null, + fk_group int8 not null, + primary key (id) +); + +create table o_lecture_block_roll_call ( + id bigserial not null, + creationdate timestamp not null, + lastmodified timestamp not null, + l_comment text, + l_log text, + l_lectures_attended varchar(128), + l_lectures_absent varchar(128), + l_lectures_attended_num int8 not null default 0, + l_lectures_absent_num int8 not null default 0, + l_absence_reason text, + l_absence_authorized bool default null, + l_absence_appeal_date timestamp, + fk_lecture_block int8 not null, + fk_identity int8 not null, + primary key (id) +); + +create table o_lecture_participant_summary ( + id bigserial not null, + creationdate timestamp not null, + lastmodified timestamp not null, + l_attendance_rate float(24) default null, + l_first_admission_date timestamp default null, + l_attended_lectures int8 not null default 0, + l_absent_lectures int8 not null default 0, + l_excused_lectures int8 not null default 0, + l_planneds_lectures int8 not null default 0, + fk_entry int8 not null, + fk_identity int8 not null, + primary key (id), + unique (fk_entry, fk_identity) +); + +create table o_lecture_entry_config ( + id bigserial not null, + creationdate timestamp not null, + lastmodified timestamp not null, + l_lecture_enabled bool default null, + l_override_module_def bool default false, + l_rollcall_enabled bool default null, + l_calculate_attendance_rate bool default null, + l_required_attendance_rate float(24) default null, + l_sync_calendar_teacher bool default null, + l_sync_calendar_participant bool default null, + fk_entry int8 not null, + unique(fk_entry), + primary key (id) +); + -- user view create view o_bs_identity_short_v as ( select @@ -2753,6 +2846,32 @@ create index cer_uuid_idx on o_cer_certificate (c_uuid); alter table o_sms_message_log add constraint sms_log_to_identity_idx foreign key (fk_identity) references o_bs_identity (id); create index idx_sms_log_to_identity_idx on o_sms_message_log(fk_identity); +-- lectures +alter table o_lecture_block add constraint lec_block_entry_idx foreign key (fk_entry) references o_repositoryentry (repositoryentry_id); +create index idx_lec_block_entry_idx on o_lecture_block(fk_entry); +alter table o_lecture_block add constraint lec_block_gcoach_idx foreign key (fk_teacher_group) references o_bs_group (id); +create index idx_lec_block_gcoach_idx on o_lecture_block(fk_teacher_group); +alter table o_lecture_block add constraint lec_block_reason_idx foreign key (fk_reason) references o_lecture_reason (id); +create index idx_lec_block_reason_idx on o_lecture_block(fk_reason); + +alter table o_lecture_block_to_group add constraint lec_block_to_block_idx foreign key (fk_group) references o_bs_group (id); +create index idx_lec_block_to_block_idx on o_lecture_block_to_group(fk_group); +alter table o_lecture_block_to_group add constraint lec_block_to_group_idx foreign key (fk_lecture_block) references o_lecture_block (id); +create index idx_lec_block_to_group_idx on o_lecture_block_to_group(fk_lecture_block); + +alter table o_lecture_block_roll_call add constraint lec_call_block_idx foreign key (fk_lecture_block) references o_lecture_block (id); +create index idx_lec_call_block_idx on o_lecture_block_roll_call(fk_lecture_block); +alter table o_lecture_block_roll_call add constraint lec_call_identity_idx foreign key (fk_identity) references o_bs_identity (id); +create index idx_lec_call_identity_idx on o_lecture_block_roll_call(fk_identity); + +alter table o_lecture_participant_summary add constraint lec_part_entry_idx foreign key (fk_entry) references o_repositoryentry (repositoryentry_id); +create index idx_lec_part_entry_idx on o_lecture_participant_summary(fk_entry); +alter table o_lecture_participant_summary add constraint lec_part_ident_idx foreign key (fk_identity) references o_bs_identity (id); +create index idx_lec_part_ident_idx on o_lecture_participant_summary(fk_identity); + +alter table o_lecture_entry_config add constraint lec_entry_config_entry_idx foreign key (fk_entry) references o_repositoryentry (repositoryentry_id); +create index idx_lec_entry_conf_entry_idx on o_lecture_entry_config(fk_entry); + -- o_logging_table create index log_target_resid_idx on o_loggingtable(targetresid); create index log_ptarget_resid_idx on o_loggingtable(parentresid); -- GitLab