From 689b85ad0868087d5f5163f9020ba035a557fe82 Mon Sep 17 00:00:00 2001 From: Steffen Jost Date: Thu, 22 Aug 2019 15:34:29 +0200 Subject: [PATCH 1/2] feat(allocations): add info page for allocations detailed information about allocations added; not yet open allocations display time until opening now --- messages/uniworx/de.msg | 8 ++- routes | 1 + src/Foundation.hs | 13 +++- src/Handler/Allocation.hs | 1 + src/Handler/Allocation/Info.hs | 13 ++++ src/Handler/Allocation/Show.hs | 8 ++- templates/allocation/show.hamlet | 54 +++++++++------ templates/default-layout.lucius | 6 +- templates/i18n/allocation-info/de.hamlet | 85 ++++++++++++++++++++++++ 9 files changed, 161 insertions(+), 28 deletions(-) create mode 100644 src/Handler/Allocation/Info.hs create mode 100644 templates/i18n/allocation-info/de.hamlet diff --git a/messages/uniworx/de.msg b/messages/uniworx/de.msg index 4df1b31f1..dfa9c0138 100644 --- a/messages/uniworx/de.msg +++ b/messages/uniworx/de.msg @@ -1018,6 +1018,7 @@ MenuExamEdit: Bearbeiten MenuExamUsers: Teilnehmer MenuExamAddMembers: Prüfungsteilnehmer hinzufügen MenuLecturerInvite: Dozenten hinzufügen +MenuAllocationInfo: Hinweise zum Ablauf einer Zentralanmeldung AuthPredsInfo: Um eigene Veranstaltungen aus Sicht der Teilnehmer anzusehen, können Veranstalter und Korrektoren hier die Prüfung ihrer erweiterten Berechtigungen temporär deaktivieren. Abgewählte Prädikate schlagen immer fehl. Abgewählte Prädikate werden also nicht geprüft um Zugriffe zu gewähren, welche andernfalls nicht erlaubt wären. Diese Einstellungen gelten nur temporär bis Ihre Sitzung abgelaufen ist, d.h. bis ihr Browser-Cookie abgelaufen ist. Durch Abwahl von Prädikaten kann man sich höchstens temporär aussperren. AuthPredsActive: Aktive Authorisierungsprädikate @@ -1474,8 +1475,11 @@ AllocationStaffRegisterFrom: Eintragung der Kurse ab AllocationStaffRegister: Eintragung der Kurse AllocationRegisterFrom: Bewerbung ab AllocationRegister: Bewerbung +AllocationRegisterClosed: Die Zentralanmeldung ist aktuell geschlossen. +AllocationRegisterOpensIn opens@Text: Die Zentralanmeldung öffnet voraussichtlich in #{opens} AllocationStaffAllocationFrom: Bewertung der Bewerbungen ab -AllocationStaffAllocation: Bewertung der Bewerbungen +AllocationStaffAllocation: Bewerbungsbewertung +AllocationProcess: Platzvergabe AllocationNoApplication: Keine Bewerbung AllocationPriority: Priorität AllocationPriorityTip: Kurse, denen Sie eine höhere Priorität zuteilen, werden bei der Platzvergabe präferiert. @@ -1489,7 +1493,7 @@ BtnAllocationRegister: Teilnahme registrieren BtnAllocationRegistrationEdit: Teilnahme anpassen AllocationParticipation: Teilnahme an der Zentralanmeldung AllocationParticipationLoginFirst: Um an der Zentralanmeldung teilzunehmen, loggen Sie sich bitte zunächst ein. -AllocationCourses: Kurse +AllocationCourses: Kurse dieser Zentralanmeldung AllocationData: Organisatorisches AllocationCoursePriority i@Natural: #{i}. Wahl AllocationCourseNoApplication: Keine Bewerbung diff --git a/routes b/routes index 4e2e5317c..e1b8714b3 100644 --- a/routes +++ b/routes @@ -61,6 +61,7 @@ /info InfoR GET !free /info/lecturer InfoLecturerR GET !lecturer /info/data DataProtR GET !free +/info/allocation InfoAllocationR GET !free /impressum ImpressumR GET !free /version VersionR GET !free diff --git a/src/Foundation.hs b/src/Foundation.hs index 3ccd28aae..c6e9be2f2 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -1724,6 +1724,7 @@ instance YesodBreadcrumbs UniWorX where breadcrumb InfoR = return ("Information" , Nothing) breadcrumb InfoLecturerR = return ("Veranstalter" , Just InfoR) breadcrumb DataProtR = return ("Datenschutz" , Just InfoR) + breadcrumb InfoAllocationR = return ("Zentralanmeldungen", Just InfoR) breadcrumb ImpressumR = return ("Impressum" , Just InfoR) breadcrumb VersionR = return ("Versionsgeschichte", Just InfoR) @@ -2173,7 +2174,7 @@ pageActions (TermCourseListR tid) = ] pageActions (TermSchoolCourseListR _tid _ssh) = [ MenuItem - { menuItemType = PageActionPrime + { menuItemType = PageActionPrime , menuItemLabel = MsgMenuCourseNew , menuItemIcon = Just "book" , menuItemRoute = SomeRoute CourseNewR @@ -2181,6 +2182,16 @@ pageActions (TermSchoolCourseListR _tid _ssh) = , menuItemAccessCallback' = return True } ] +pageActions (AllocationR _tid _ssh _ash AShowR) = + [ MenuItem + { menuItemType = PageActionPrime + , menuItemLabel = MsgMenuAllocationInfo + , menuItemIcon = Nothing + , menuItemRoute = SomeRoute InfoAllocationR + , menuItemModal = True + , menuItemAccessCallback' = return True + } + ] pageActions (CourseListR) = [ MenuItem { menuItemType = PageActionPrime diff --git a/src/Handler/Allocation.hs b/src/Handler/Allocation.hs index 286a87aa1..231b33b46 100644 --- a/src/Handler/Allocation.hs +++ b/src/Handler/Allocation.hs @@ -2,6 +2,7 @@ module Handler.Allocation ( module Handler.Allocation ) where +import Handler.Allocation.Info as Handler.Allocation import Handler.Allocation.Show as Handler.Allocation import Handler.Allocation.Application as Handler.Allocation import Handler.Allocation.Register as Handler.Allocation diff --git a/src/Handler/Allocation/Info.hs b/src/Handler/Allocation/Info.hs new file mode 100644 index 000000000..d3140d75a --- /dev/null +++ b/src/Handler/Allocation/Info.hs @@ -0,0 +1,13 @@ +module Handler.Allocation.Info + ( getInfoAllocationR + ) where + +import Import +import Handler.Utils + + +getInfoAllocationR :: Handler Html +getInfoAllocationR = do + siteLayoutMsg MsgMenuAllocationInfo $ do + setTitleI MsgMenuAllocationInfo + $(i18nWidgetFile "allocation-info") diff --git a/src/Handler/Allocation/Show.hs b/src/Handler/Allocation/Show.hs index 53149712a..6cfe5e19a 100644 --- a/src/Handler/Allocation/Show.hs +++ b/src/Handler/Allocation/Show.hs @@ -10,18 +10,19 @@ import Handler.Allocation.Register import Handler.Allocation.Application import qualified Database.Esqueleto as E - + getAShowR :: TermId -> SchoolId -> AllocationShorthand -> Handler Html getAShowR tid ssh ash = do muid <- maybeAuthId + now <- liftIO getCurrentTime let resultCourse :: Simple Field1 a (Entity Course) => Lens' a (Entity Course) resultCourse = _1 resultCourseApplication :: Simple Field2 a (Maybe (Entity CourseApplication)) => Traversal' a (Entity CourseApplication) resultCourseApplication = _2 . _Just - resultHasTemplate :: Simple Field3 a (E.Value Bool) => Lens' a Bool + resultHasTemplate :: Simple Field3 a (E.Value Bool) => Lens' a Bool resultHasTemplate = _3 . _Value (Entity aId Allocation{..}, courses, registration) <- runDB $ do @@ -92,5 +93,6 @@ getAShowR tid ssh ash = do
^{wdgt} |] - + let daysToRegistrationStart = assertM (>0) $ (`diffUTCTime` now) <$> allocationRegisterFrom + allocationInfoModal = modal [whamlet|_{MsgMenuAllocationInfo}|] $ Left $ SomeRoute InfoAllocationR $(widgetFile "allocation/show") diff --git a/templates/allocation/show.hamlet b/templates/allocation/show.hamlet index d2406a677..29024f2e4 100644 --- a/templates/allocation/show.hamlet +++ b/templates/allocation/show.hamlet @@ -34,26 +34,42 @@ $newline never
^{formatTimeRangeW SelFormatDateTime fromT allocationStaffAllocationTo} -$if is _Just muid - $if mayRegister || is _Just registration -
-

- _{MsgAllocationParticipation} - $if mayRegister - ^{registerForm'} - $else - $maybe Entity _ AllocationUser{allocationUserTotalCourses} <- registration -
-
- _{MsgAllocationTotalCourses} -
- #{allocationUserTotalCourses} -$else -
-

- _{MsgAllocationParticipation} + $# TODO show datetime of automatic allocation + $# + $#
+ $# _{MsgAllocationProcess} + $#
+ $# ^{formatTimeRangeW SelFormatDateTime fromT allocationProcess} + $# + +
+

+ _{MsgAllocationParticipation} + $if is _Nothing muid

- _{MsgAllocationParticipationLoginFirst} + _{MsgAllocationParticipationLoginFirst} + $elseif mayRegister + $# existing registrations may also be edited in this case + ^{registerForm'} + $elseif is _Just registration + $# show existing registration even if it cannot be changed now + $maybe Entity _ AllocationUser{allocationUserTotalCourses} <- registration +

+
+ _{MsgAllocationTotalCourses} +
+ #{allocationUserTotalCourses} + $else + $# Provide helpful information for confused students who cannot register now + $maybe daysToOpen <- daysToRegistrationStart +

+ _{MsgAllocationRegisterOpensIn (formatDiffDays daysToOpen)} + $nothing +

+ _{MsgAllocationRegisterClosed} +

+ $# This redundant links prevents useless help requests from frantic users + ^{allocationInfoModal} $if not (null courseWidgets)

diff --git a/templates/default-layout.lucius b/templates/default-layout.lucius index 577e95c01..b3b493fd5 100644 --- a/templates/default-layout.lucius +++ b/templates/default-layout.lucius @@ -184,10 +184,10 @@ h4 { &:last-child { margin: 0.5rem 0 0; - } - &:first-of-type { - margin: 0; + &:first-of-type { + margin: 0; + } } } } diff --git a/templates/i18n/allocation-info/de.hamlet b/templates/i18n/allocation-info/de.hamlet new file mode 100644 index 000000000..e1379d2c4 --- /dev/null +++ b/templates/i18n/allocation-info/de.hamlet @@ -0,0 +1,85 @@ +$newline text +
+

+ Jede Zentralanmeldung durchläuft + der Reihe nach folgende Phasen: +

+
+ _{MsgAllocationStaffRegister} +
+

+ Veranstalter können nur in diesem Zeitraum ihre Veranstaltungen + zur Zentralanmeldung hinzufügen oder entfernen. +

+ Pro Veranstaltung wird einzeln festgelegt, + ob Studierende einen Bewerbungstext und/oder Bewerbungsdateien + einreichen sollen. + Veranstalter stellen auch Anweisungen zur Bewerbung ein, + z.B. welchen Inhalt abzugebende Bewerbungsdateien enthalten sollen. +

+ Zur Zentralanmeldung eingetragene Kurse + erlauben während dem gesamten Ablauf + der Zentralanmeldung keine anderweitigen Kursanmeldung mehr, + auch nicht durch den Veranstalter selbst. + +

+ _{MsgAllocationRegister} +
+

+ Studierende können sich nur in diesem Zeitraum + auf Plätze in Kursen einer Zentralanmeldung bewerben. +

+ Bewerber können jedem Kurs der Zentralanmeldung eine Priorität + zuweisen, zwischen "dieser Kurs wäre meine erste Wahl" + und "diesen Kurs besuche ich auf keinen Fall". + Es kann auch mehreren Kursen die gleiche Priorität eingeräumt werden. +

+ Studierende können auch mehr als einen Platz + in verschiedenen Kursen einer Zentralanmeldung anfordern, + falls die Kurskapazitäten und/oder Dringlichkeit ausreichend sind. +

+ Bewerbungstexte und/oder Bewerbungsdateien + sind pro Kurs anzugeben, falls vom Veranstalter gefordert. + +

+ _{MsgAllocationStaffAllocation} +
+

+ Veranstalter können nur in diesem Zeitraum die + Bewerbungen einsehen und bewerten. + $#

+ $# Nur in manchen Zentralanmeldungen dürfen Veranstalter + $# Bewerber jetzt direkt ablehnen und/oder übernehmen. + $#

+ $# Veranstalter haben noch eine letzte Möglichkeit, + $# die Kurskapazität anzupassen. + +

+ _{MsgAllocationProcess} +
+

+ Die Plätze werden gemäß Studienfortschritt, Dringlichkeit + und der Bewertung durch den Veranstalter auf die Bewerber verteilt. +

+ Die Bewerber werden diekt in den jeweiligen Kursen angemeldet. + Eine Abmeldung durch Studierende ist nicht erlaubt. + Übernommene Bewerber, welche einen zugeteilten Platz + ohne Angabe eines triftigen Grundes nicht antreten, + werden in zukünftigen Zentralanmeldungen + unter Umständen benachteiligt. +

+ Veranstalter können frühestens nach der erfolgten Zuteilung + Teilnehmer selbst an-/abmelden + und ggf. Nachrücker für freigewordene Plätze anfordern. + +

+ Der Ablauf einer Zentralanmeldung kann unter Umständen noch variieren. + + Insbesondere: # + Fehlt in der Übersichtsseite einer Zentralanmeldung + die Angabe einer dieser Phasen, dann wurde der entsprechende Zeitraum + leider noch nicht festgelegt! +

+ Mehrere Zentralanmeldungen werden völlig unabhängig voneinander + abgewickelt. + From 754d6caa1ba056de70ba5fa868a1a4f3976876f9 Mon Sep 17 00:00:00 2001 From: Steffen Jost Date: Thu, 22 Aug 2019 16:41:26 +0200 Subject: [PATCH 2/2] fix(course list): show complete registration span show regFrom to regTo, as requested by user feedback, or link to allocation Closes #446 --- messages/uniworx/de.msg | 1 + src/Handler/Course/List.hs | 22 +++++++++++---------- src/Handler/Utils/Table/Pagination.hs | 11 ++++++----- templates/table/course/course-teaser.hamlet | 13 +++++++++--- 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/messages/uniworx/de.msg b/messages/uniworx/de.msg index dfa9c0138..0d7a1a441 100644 --- a/messages/uniworx/de.msg +++ b/messages/uniworx/de.msg @@ -30,6 +30,7 @@ Aborted: Abgebrochen Remarks: Hinweise Registered: Angemeldet RegisteredSince: Angemeldet seit +Registration: Anmeldung RegisterFrom: Anmeldungen von RegisterTo: Anmeldungen bis DeRegUntil: Abmeldungen bis diff --git a/src/Handler/Course/List.hs b/src/Handler/Course/List.hs index 4a29f3851..00395a855 100644 --- a/src/Handler/Course/List.hs +++ b/src/Handler/Course/List.hs @@ -26,39 +26,39 @@ import qualified Database.Esqueleto.Utils as E -- NOTE: Outdated way to use dbTable; see ProfileDataR Handler for a more recent method. -type CourseTableData = DBRow (Entity Course, Int, Bool, Entity School, [Entity User]) +type CourseTableData = DBRow (Entity Course, Int, Bool, Entity School, [Entity User], Maybe (Entity Allocation)) colCourse :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a) colCourse = sortable (Just "course") (i18nCell MsgCourse) - $ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, _, _) } -> + $ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, _, _, _) } -> anchorCell (CourseR courseTerm courseSchool courseShorthand CShowR) [whamlet|_{courseName}|] colDescription :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a) colDescription = sortable Nothing mempty - $ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, _, _) } -> + $ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, _, _, _) } -> case courseDescription of Nothing -> mempty (Just descr) -> cell $ modal (toWidget $ hasComment True) (Right $ toWidget descr) colCShort :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a) colCShort = sortable (Just "cshort") (i18nCell MsgCourseShort) - $ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, _, _) } -> + $ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, _, _, _) } -> anchorCell (CourseR courseTerm courseSchool courseShorthand CShowR) [whamlet|_{courseShorthand}|] colTerm :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a) colTerm = sortable (Just "term") (i18nCell MsgTerm) - $ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, _, _) } -> + $ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, _, _, _) } -> anchorCell (TermCourseListR courseTerm) [whamlet|#{courseTerm}|] colSchoolShort :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a) colSchoolShort = sortable (Just "schoolshort") (i18nCell MsgCourseSchoolShort) - $ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, Entity _ School{..}, _) } -> + $ \DBRow{ dbrOutput=(Entity _ Course{..}, _, _, Entity _ School{..}, _, _) } -> anchorCell (TermSchoolCourseListR courseTerm courseSchool) [whamlet|_{schoolShorthand}|] colRegistered :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a) colRegistered = sortable (Just "registered") (i18nCell MsgRegistered) - $ \DBRow{ dbrOutput=(_, _, registered, _, _) } -> tickmarkCell registered + $ \DBRow{ dbrOutput=(_, _, registered, _, _, _) } -> tickmarkCell registered type CourseTableExpr = E.SqlExpr (Entity Course) `E.InnerJoin` E.SqlExpr (Entity School) @@ -91,7 +91,9 @@ makeCourseTable whereClause colChoices psValidator = do dbtProj :: DBRow _ -> MaybeT (ReaderT SqlBackend (HandlerT UniWorX IO)) CourseTableData dbtProj = traverse $ \(course, E.Value participants, E.Value registered, school) -> do lecturerList <- lift $ E.select $ E.from $ lecturerQuery $ E.val $ entityKey course - return (course, participants, registered, school, lecturerList) + courseAlloc <- lift $ getBy (UniqueAllocationCourse $ entityKey course) + >>= traverse (getJustEntity . allocationCourseAllocation . entityVal) + return (course, participants, registered, school, lecturerList, courseAlloc) snd <$> dbTable psValidator DBTable { dbtSQLQuery , dbtRowKey = \(course `E.InnerJoin` _) -> course E.^. CourseId @@ -165,8 +167,8 @@ makeCourseTable whereClause colChoices psValidator = do ] , dbtStyle = def { dbsFilterLayout = defaultDBSFilterLayout - , dbsTemplate = DBSTCourse (_dbrOutput . _1) (_dbrOutput . _5) (_dbrOutput . _3) (_dbrOutput . _4) - -- ^ course ^ lecturer list ^ isRegistered ^ school + , dbsTemplate = DBSTCourse (_dbrOutput . _1) (_dbrOutput . _5) (_dbrOutput . _3) (_dbrOutput . _4) (_dbrOutput . _6 . _Just) + -- ^ course ^ lecturer list ^ isRegistered ^ school ^ allocation } , dbtParams = def , dbtIdent = "courses" :: Text diff --git a/src/Handler/Utils/Table/Pagination.hs b/src/Handler/Utils/Table/Pagination.hs index ff5e907f3..c3dc41c01 100644 --- a/src/Handler/Utils/Table/Pagination.hs +++ b/src/Handler/Utils/Table/Pagination.hs @@ -105,7 +105,7 @@ import Data.Semigroup as Sem (Semigroup(..)) import qualified Data.Conduit.List as C -import Handler.Utils.DateTime (formatTimeW) +import Handler.Utils.DateTime (formatTimeRangeW) import qualified Control.Monad.Catch as Catch @@ -444,7 +444,7 @@ data DBStyle r = DBStyle } data DBSTemplateMode r = DBSTDefault - | DBSTCourse (Lens' r (Entity Course)) (Lens' r [Entity User]) (Lens' r Bool) (Lens' r (Entity School)) + | DBSTCourse (Lens' r (Entity Course)) (Lens' r [Entity User]) (Lens' r Bool) (Lens' r (Entity School)) (Traversal' r (Entity Allocation)) instance Default (DBStyle r) where def = DBStyle @@ -1045,12 +1045,12 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db attrs = sortableContent ^. cellAttrs piSorting' = [ sSet | sSet <- fromMaybe [] piSorting, Just (sortKey sSet) /= sortableKey ] case dbsTemplate of - DBSTCourse _ _ _ _ -> return $(widgetFile "table/course/header") - DBSTDefault -> return $(widgetFile "table/cell/header") + DBSTCourse{} -> return $(widgetFile "table/course/header") + DBSTDefault -> return $(widgetFile "table/cell/header") in do wHeaders <- maybe (return Nothing) (fmap Just . genHeaders) pSortable case dbsTemplate of - DBSTCourse c l r s -> do + DBSTCourse c l r s a -> do wRows <- forM rows $ \row' -> let Course{..} = row' ^. c . _entityVal lecturerUsers = row' ^. l @@ -1058,6 +1058,7 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db isRegistered = row' ^. r courseSchoolName = schoolName $ row' ^. s . _entityVal courseSemester = (termToText . unTermKey) courseTerm + courseAllocation = row' ^? a in return $(widgetFile "table/course/course-teaser") return $(widgetFile "table/course/colonnade") DBSTDefault -> do diff --git a/templates/table/course/course-teaser.hamlet b/templates/table/course/course-teaser.hamlet index b4f50fe97..f9a3f2a71 100644 --- a/templates/table/course/course-teaser.hamlet +++ b/templates/table/course/course-teaser.hamlet @@ -17,9 +17,16 @@ $forall lecturer <- courseLecturers

  • #{lecturer} - $maybe regTo <- courseRegisterTo -
    _{MsgRegisterTo} -
    ^{formatTimeW SelFormatDateTime regTo} + $maybe Entity _ Allocation{allocationTerm, allocationSchool, allocationShorthand, allocationName} <- courseAllocation +