Compare commits
3 Commits
stundenpla
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b0029ba04 | |||
| e554048f5a | |||
| e59fff352f |
@ -282,16 +282,9 @@ user-defaults:
|
||||
date-time-format: "%d.%m.%Y %R"
|
||||
date-format: "%d.%m.%y"
|
||||
time-format: "%R"
|
||||
week-start: Monday
|
||||
download-files: false
|
||||
warning-days: 1209600
|
||||
show-sex: false
|
||||
schedule-view: week
|
||||
schedule-week-days: [Monday,Tuesday,Wednesday,Thursday,Friday]
|
||||
schedule-week-time-from: 28800 # 08:00
|
||||
schedule-week-time-to: 72000 # 20:00
|
||||
schedule-week-timeslot-length: 7200 # 2h
|
||||
schedule-occurrence-display-default: true
|
||||
exam-office-get-synced: true
|
||||
exam-office-get-labels: true
|
||||
prefers-postal: true
|
||||
|
||||
@ -263,13 +263,6 @@ button:not(.btn-link),
|
||||
&.btn-danger
|
||||
background-color: var(--color-error-dark)
|
||||
|
||||
.fa,.fas
|
||||
color: white
|
||||
.tooltip__handle
|
||||
cursor: pointer
|
||||
.tooltip__content
|
||||
color: var(--color-font)
|
||||
|
||||
.buttongroup
|
||||
display: grid
|
||||
grid: min-content / auto-flow max-content
|
||||
@ -287,8 +280,6 @@ button[disabled]:not(.btn-link),
|
||||
opacity: 0.3
|
||||
background-color: var(--color-grey)
|
||||
cursor: default
|
||||
.tooltip__handle
|
||||
cursor: default
|
||||
|
||||
input[type="submit"]:not([disabled]):not(.btn-link):hover,
|
||||
input[type="button"]:not([disabled]):not(.btn-link):hover,
|
||||
@ -1703,63 +1694,6 @@ video
|
||||
object-fit: contain
|
||||
flex-grow: 1
|
||||
|
||||
table.schedule
|
||||
.schedule--entry
|
||||
background-color: var(--color-dark)
|
||||
color: white
|
||||
font-weight: 600
|
||||
padding: 10px
|
||||
|
||||
&__ends
|
||||
border-radius: 0 0 15px 15px
|
||||
background: linear-gradient(0turn, var(--color-dark), var(--color-dark) calc(100% - 7px), rgba(0,0,0,0))
|
||||
&__begins
|
||||
border-radius: 15px 15px 0 0
|
||||
background: linear-gradient(0.5turn, var(--color-dark), var(--color-dark) calc(100% - 7px), rgba(0,0,0,0))
|
||||
&__contained
|
||||
border-radius: 15px
|
||||
&__intersects
|
||||
background: linear-gradient(0turn, rgba(0,0,0,0), var(--color-dark) 7px, var(--color-dark) calc(100% - 7px), rgba(0,0,0,0))
|
||||
|
||||
&__continuation
|
||||
font-style: italic
|
||||
|
||||
a.schedule--entry-link
|
||||
text-decoration: none
|
||||
a.schedule--entry-link + a.schedule--entry-link > .schedule--entry
|
||||
margin-top: 5px
|
||||
a
|
||||
color: white
|
||||
|
||||
.table__th.schedule-current
|
||||
background-color: var(--color-primary)
|
||||
|
||||
.schedule--entry.schedule-current
|
||||
@keyframes schedule-current--blink
|
||||
50%
|
||||
opacity: .85
|
||||
animation: schedule-current--blink 2s linear infinite
|
||||
|
||||
form.schedule-options
|
||||
--schedule-option-radius: 20px 50%
|
||||
|
||||
display: flex
|
||||
justify-content: center
|
||||
|
||||
button[name=schedule-options]
|
||||
height: 45px
|
||||
|
||||
white-space: nowrap
|
||||
|
||||
& > div:first-of-type
|
||||
button[name=schedule-options]
|
||||
border-top-left-radius: var(--schedule-option-radius)
|
||||
border-bottom-left-radius: var(--schedule-option-radius)
|
||||
& > div:last-of-type
|
||||
button[name=schedule-options]
|
||||
border-top-right-radius: var(--schedule-option-radius)
|
||||
border-bottom-right-radius: var(--schedule-option-radius)
|
||||
|
||||
.hr
|
||||
height: 1px
|
||||
width: 90%
|
||||
@ -1826,4 +1760,4 @@ form.schedule-options
|
||||
color: var(--color-lightwhite)
|
||||
&.nonactive
|
||||
background-color: var(--color-nonactive)
|
||||
color: var(--color-nonactive-dark)
|
||||
color: var(--color-nonactive-dark)
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
#messages or constructors that are used all over the code
|
||||
|
||||
Logo !ident-ok: FRADrive
|
||||
EmailInvitationWarning: Diese Adresse konnte keinem FRADrive-Benutzer/-Benutzerin zugeordnet werden. Es wird eine Einladung per E-Mail versandt.
|
||||
BoolIrrelevant !ident-ok: —
|
||||
@ -29,4 +31,4 @@ PaginationPage: Angzeigte Seite
|
||||
PaginationError: Paginierung Parameter dürfen nicht negativ sein
|
||||
|
||||
NullDeletes: Zum Löschen NULL eingeben.
|
||||
SortPriority: Sortierungsprioritätz
|
||||
SortPriority: Sortierungspriorität
|
||||
@ -2,6 +2,8 @@
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
#messages or constructors that are used all over the Code
|
||||
|
||||
Logo: FRADrive
|
||||
EmailInvitationWarning: This address could not be matched to any FRADrive user. An invitation will be sent via email.
|
||||
BoolIrrelevant: —
|
||||
|
||||
@ -9,7 +9,7 @@ DegreeCourse json -- for which degree programmes this course is appropriate fo
|
||||
UniqueDegreeCourse course degree terms
|
||||
deriving Generic
|
||||
Course -- Information about a single course; contained info is always visible to all users
|
||||
name CourseName
|
||||
name (CI Text)
|
||||
description StoredMarkup Maybe -- user-defined large Html, ought to contain module description
|
||||
linkExternal URI Maybe -- arbitrary user-defined url for external course page
|
||||
shorthand (CI Text) -- practical shorthand of course name, used for identification
|
||||
@ -27,17 +27,8 @@ Course -- Information about a single course; contained info is always visible
|
||||
TermSchoolCourseShort term school shorthand -- shorthand must be unique within school and semester
|
||||
TermSchoolCourseName term school name -- name must be unique within school and semester
|
||||
deriving Generic
|
||||
|
||||
CourseScheduleOpt -- opt-in/-out for displaying occurrence related to this course (may be overriden by specific occurrence opts)
|
||||
course CourseId
|
||||
user UserId
|
||||
opt Bool
|
||||
UniqueCourseScheduleOpt course user
|
||||
deriving Generic
|
||||
|
||||
CourseEvent
|
||||
type CourseEventType
|
||||
course CourseId
|
||||
type (CI Text)
|
||||
course CourseId OnDeleteCascade OnUpdateCascade
|
||||
room RoomReference Maybe
|
||||
roomHidden Bool default=false
|
||||
@ -45,12 +36,6 @@ CourseEvent
|
||||
note StoredMarkup Maybe
|
||||
lastChanged UTCTime default=now()
|
||||
deriving Generic
|
||||
CourseEventScheduleOpt -- opt-in/-out for course event display in a user's schedule (TODO: currently for all occurrences of a course event; separate opt-ins/-outs per occurrence in CourseEventTime instead?)
|
||||
courseEvent CourseEventId
|
||||
user UserId
|
||||
opt Bool -- whether the course event should be displayed; False <=> opt-out, True <=> opt-in
|
||||
UniqueCourseEventScheduleOpt courseEvent user
|
||||
deriving Generic
|
||||
|
||||
CourseAppInstructionFile
|
||||
course CourseId OnDeleteCascade OnUpdateCascade
|
||||
|
||||
@ -47,12 +47,6 @@ ExamOccurrence
|
||||
description StoredMarkup Maybe
|
||||
UniqueExamOccurrence exam name
|
||||
deriving Generic
|
||||
ExamOccurrenceScheduleOpt
|
||||
examOccurrence ExamOccurrenceId
|
||||
user UserId
|
||||
opt Bool
|
||||
UniqueExamOccurrenceScheduleOpt examOccurrence user
|
||||
deriving Generic
|
||||
ExamRegistration
|
||||
exam ExamId
|
||||
user UserId
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
-- SPDX-FileCopyrightText: 2022-2024 Sarah Vaupel <sarah.vaupel@uniworx.de>, Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <vaupel.sarah@campus.lmu.de>
|
||||
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <vaupel.sarah@campus.lmu.de>
|
||||
--
|
||||
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
Tutorial json
|
||||
name TutorialName
|
||||
course CourseId OnDeleteCascade OnUpdateCascade
|
||||
type TutorialType -- "Tutorium", "Zentralübung", ...
|
||||
type (CI Text) -- "Tutorium", "Zentralübung", ...
|
||||
capacity Int Maybe -- limit for enrolment in this tutorial
|
||||
room RoomReference Maybe
|
||||
roomHidden Bool default=false
|
||||
@ -29,10 +29,4 @@ TutorialParticipant
|
||||
user UserId
|
||||
UniqueTutorialParticipant tutorial user
|
||||
deriving Eq Ord Show
|
||||
deriving Generic
|
||||
TutorialScheduleOpt
|
||||
tutorial TutorialId
|
||||
user UserId
|
||||
opt Bool
|
||||
UniqueTutorialScheduleOpt tutorial user
|
||||
deriving Generic
|
||||
deriving Generic
|
||||
@ -1,4 +1,4 @@
|
||||
-- SPDX-FileCopyrightText: 2022-2024 Sarah Vaupel <sarah.vaupel@uniworx.de>, Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <sarah.vaupel@ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>,Steffen Jost <s.jost@fraport.de>
|
||||
-- SPDX-FileCopyrightText: 2022-24 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <sarah.vaupel@ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>,Steffen Jost <s.jost@fraport.de>
|
||||
--
|
||||
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@ -32,7 +32,6 @@ User json -- Each Uni2work user has a corresponding row in this table; create
|
||||
dateTimeFormat DateTimeFormat "default='%d %b %y %R'" -- preferred Date+Time display format for user; user-defined
|
||||
dateFormat DateTimeFormat "default='%d %b %Y'" -- preferred Date-only display format for user; user-defined
|
||||
timeFormat DateTimeFormat "default='%R'" -- preferred Time-only display format for user; user-defined
|
||||
weekStart DayOfWeek default='monday' -- preferred first day of week for user; user-defined
|
||||
downloadFiles Bool default=false -- Should files be opened in browser or downloaded? (users often oblivious that their browser has a setting for this)
|
||||
languages Languages Maybe -- Preferred language; user-defined
|
||||
notificationSettings NotificationSettings "default='{}'::jsonb" -- Bit-array for which events email notifications are requested by user; user-defined; missing fields in json object will be parsed to default trigger
|
||||
@ -41,12 +40,6 @@ User json -- Each Uni2work user has a corresponding row in this table; create
|
||||
sex Sex Maybe -- currently ignored
|
||||
birthday Day Maybe -- for better identification
|
||||
showSex Bool default=false
|
||||
scheduleView ScheduleView default='week'
|
||||
scheduleWeekDays ScheduleWeekDays default='["monday","tuesday","wednesday","thursday","friday"]'::jsonb -- which weekdays to display by default; if there is an occurrence to display for a weekday that is not mentioned here, the weekday will be displayed regardless
|
||||
scheduleWeekTimeFrom NominalDiffTime default=28800 -- start of the first time slot to display in weekly schedule by default (i.e. regardless of the existence of occurrences in this slot)
|
||||
scheduleWeekTimeTo NominalDiffTime default=72000 -- end of the last time slot to display in weekly schedule by default (i.e. regardless of the existence of occurrences in this slot)
|
||||
scheduleWeekTimeslotLength NominalDiffTime default=7200 -- length of one timeslot
|
||||
scheduleOccurrenceDisplayDefault Bool default=True -- whether occurrences from new courses should be displayed in the schedule by default
|
||||
telephone Text Maybe
|
||||
mobile Text Maybe
|
||||
companyPersonalNumber Text Maybe -- Company will become a new table, but if company=fraport, some information is received via LDAP
|
||||
|
||||
16
routes
16
routes
@ -170,10 +170,7 @@
|
||||
/course/#TermId/#SchoolId/#CourseShorthand CourseR !lecturer:
|
||||
/ CShowR GET !tutor !corrector !exam-corrector !course-registered !course-time !evaluation !exam-office
|
||||
/favourite CFavouriteR GET POST !free
|
||||
/schedule-opt/set/#Bool CScheduleOptSetR GET POST !free
|
||||
/schedule-opt/del CScheduleOptDelR GET POST !free
|
||||
/register CRegisterR GET POST !timeANDcapacityAND¬course-registeredANDcourse-time !timeAND¬exam-resultANDcourse-registered !lecturer
|
||||
/register-template CRegisterTemplateR GET !course-time
|
||||
/edit CEditR GET POST
|
||||
/lecturer-invite CLecInviteR GET POST
|
||||
/delete CDeleteR GET POST !lecturerANDempty
|
||||
@ -233,8 +230,6 @@
|
||||
/register TRegisterR POST !timeANDcapacityANDcourse-registeredANDregister-group !timeANDtutorial-registered
|
||||
/communication TCommR GET POST !tutor
|
||||
/tutor-invite TInviteR GET POST !tutorANDtutor-control
|
||||
/schedule-opt/set/#Bool TScheduleOptSetR GET POST !free
|
||||
/schedule-opt/del TScheduleOptDelR GET POST !free
|
||||
/exams CExamListR GET !tutor !corrector !exam-corrector !course-registered !course-time !exam-office
|
||||
/exams/new CExamNewR GET POST
|
||||
/exams/#ExamName ExamR:
|
||||
@ -246,8 +241,6 @@
|
||||
/users/invite EInviteR GET POST
|
||||
/register ERegisterR POST !timeANDcourse-registeredAND¬exam-registered !timeANDexam-registeredAND¬exam-result
|
||||
/register/#ExamOccurrenceName ERegisterOccR POST !exam-occurrence-registrationANDtimeANDcapacityANDcourse-registeredAND¬exam-occurrence-registered !exam-occurrence-registrationANDtimeANDexam-occurrence-registeredAND¬exam-result
|
||||
/schedule-opt/#ExamOccurrenceName/set/#Bool EScheduleOptSetR GET POST !free -- TODO: restrict to !timeANDcourse-registered !lecturer !tutor !corrector
|
||||
/schedule-opt/#ExamOccurrenceName/del EScheduleOptDelR GET POST !free -- TODO: see above
|
||||
/grades EGradesR GET POST !exam-office
|
||||
/assign-occurrences EAutoOccurrenceR POST
|
||||
/correct ECorrectR GET POST !exam-correctorANDtime
|
||||
@ -260,11 +253,9 @@
|
||||
!/download/*FilePath CNFileR GET !timeANDparticipant
|
||||
!/events/add CEventsNewR GET POST
|
||||
/events/#CryptoUUIDCourseEvent CourseEventR:
|
||||
/schedule-opt/set/#Bool CEvScheduleOptSetR GET POST !free
|
||||
/schedule-opt/delete CEvScheduleOptDelR GET POST !free
|
||||
/edit CEvEditR GET POST
|
||||
/delete CEvDeleteR GET POST
|
||||
/personalised-sheet-files CPersonalFilesR GET
|
||||
/edit CEvEditR GET POST
|
||||
/delete CEvDeleteR GET POST
|
||||
/personalised-sheet-files CPersonalFilesR GET
|
||||
|
||||
|
||||
/subs CorrectionsR GET POST !corrector !lecturer
|
||||
@ -279,7 +270,6 @@
|
||||
/msg/#{CryptoUUIDSystemMessage}/hide MessageHideR POST !timeANDauthentication
|
||||
|
||||
|
||||
/schedule ScheduleR GET POST !free
|
||||
/upload UploadR PUT !free
|
||||
|
||||
|
||||
|
||||
@ -144,7 +144,6 @@ import Handler.Tutorial
|
||||
import Handler.Material
|
||||
import Handler.CryptoIDDispatch
|
||||
import Handler.SystemMessage
|
||||
import Handler.Schedule
|
||||
import Handler.Health
|
||||
import Handler.Health.Interface
|
||||
import Handler.Exam
|
||||
|
||||
25
src/Audit.hs
25
src/Audit.hs
@ -1,7 +1,9 @@
|
||||
-- SPDX-FileCopyrightText: 2023 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Steffen Jost <s.jost@fraport.de>
|
||||
-- SPDX-FileCopyrightText: 2023-24 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Steffen Jost <s.jost@fraport.de>
|
||||
--
|
||||
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
{-# LANGUAGE TypeApplications #-}
|
||||
|
||||
module Audit
|
||||
( module Audit.Types
|
||||
, AuditException(..)
|
||||
@ -17,6 +19,8 @@ import Import.NoModel
|
||||
import Settings
|
||||
import Model
|
||||
import Database.Persist.Sql
|
||||
import qualified Database.Esqueleto.Experimental as E -- needs TypeApplications Lang-Pragma
|
||||
import qualified Database.Esqueleto.Utils as E
|
||||
import Audit.Types
|
||||
|
||||
import qualified Data.Text as Text
|
||||
@ -129,7 +133,7 @@ logInterface :: ( AuthId (HandlerSite m) ~ Key User
|
||||
-> Text -- ^ Any additional information
|
||||
-> ReaderT (YesodPersistBackend (HandlerSite m)) m ()
|
||||
-- ^ Log a transaction using information available from `HandlerT`, also calls `audit`
|
||||
logInterface interfaceLogInterface interfaceLogSubtype interfaceLogSuccess interfaceLogRows interfaceLogInfo = do
|
||||
logInterface interfaceLogInterface interfaceLogSubtype interfaceLogSuccess interfaceLogRows interfaceLogInfo = do
|
||||
interfaceLogWrite <- (methodGet /=) . Wai.requestMethod . reqWaiRequest <$> getRequest
|
||||
logInterface' interfaceLogInterface interfaceLogSubtype interfaceLogWrite interfaceLogSuccess interfaceLogRows interfaceLogInfo
|
||||
|
||||
@ -173,20 +177,25 @@ logInterface' (Text.strip -> interfaceLogInterface) (Text.strip -> interfaceLogS
|
||||
|
||||
reportAdminProblem :: ( IsSqlBackend (YesodPersistBackend (HandlerSite m))
|
||||
, SqlBackendCanWrite (YesodPersistBackend (HandlerSite m))
|
||||
, MonadHandler m
|
||||
, MonadHandler m
|
||||
-- , HasCallStack
|
||||
)
|
||||
=> AdminProblem -- ^ Problem to record
|
||||
=> AdminProblem -- ^ Problem to record
|
||||
-> ReaderT (YesodPersistBackend (HandlerSite m)) m ()
|
||||
-- ^ Log a problem that needs interventions by admins
|
||||
-- ^ Log a problem that needs interventions by admins, provided this problem has not already been reported and is still unsolved
|
||||
--
|
||||
-- - `problemLogTime` is now
|
||||
-- - `problemSolver` is Nothing, we do not record the person who caused it
|
||||
reportAdminProblem problem@(toJSON -> problemLogInfo) = do
|
||||
problemLogTime <- liftIO getCurrentTime
|
||||
reportAdminProblem problem = do
|
||||
let problemLogSolved = Nothing
|
||||
problemLogSolver = Nothing
|
||||
insert_ ProblemLog{..}
|
||||
problemLogInfo = toJSON problem
|
||||
problemLogTime <- liftIO getCurrentTime
|
||||
isKnown <- E.selectExists $ do
|
||||
pl <- E.from $ E.table @ProblemLog
|
||||
E.where_ $ E.isNothing (pl E.^. ProblemLogSolved)
|
||||
E.&&. E.val problemLogInfo E.==. pl E.^. ProblemLogInfo
|
||||
unless isKnown $ insert_ ProblemLog{..}
|
||||
$logWarnS "Problem" $ Text.filter (/= '\n') $ tshow problem -- <> " - " <> pack (prettyCallStack callStack)
|
||||
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
-- SPDX-FileCopyrightText: 2022-2024 Sarah Vaupel <sarah.vaupel@uniworx.de>, Gregor Kleen <gregor.kleen@ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>,Steffen Jost <s.jost@fraport.de>
|
||||
-- SPDX-FileCopyrightText: 2022-24 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>,Steffen Jost <s.jost@fraport.de>
|
||||
--
|
||||
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@ -38,7 +38,7 @@ module Database.Esqueleto.Utils
|
||||
, SqlHashable
|
||||
, sha256
|
||||
, isTrue, isFalse
|
||||
, maybe, maybe2, maybeEq, fromMaybe, guardMaybe, unsafeCoalesce
|
||||
, maybe, maybe2, maybeEq, guardMaybe, unsafeCoalesce
|
||||
, bool
|
||||
, max, min
|
||||
, greatest, least
|
||||
@ -61,7 +61,7 @@ module Database.Esqueleto.Utils
|
||||
) where
|
||||
|
||||
|
||||
import ClassyPrelude.Yesod hiding (isInfixOf, any, all, or, and, isJust, maybe, fromMaybe, bool, max, min, abs)
|
||||
import ClassyPrelude.Yesod hiding (isInfixOf, any, all, or, and, isJust, maybe, bool, max, min, abs)
|
||||
import Data.Universe
|
||||
import qualified Data.Set as Set
|
||||
import qualified Data.List as List
|
||||
@ -621,13 +621,6 @@ maybeEq a b = E.case_
|
||||
]
|
||||
(E.else_ $ a E.==. b)
|
||||
|
||||
-- TODO: replace with guardMaybe in Utils.Schedule
|
||||
fromMaybe :: (PersistField a)
|
||||
=> E.SqlExpr (E.Value a)
|
||||
-> E.SqlExpr (E.Value (Maybe a))
|
||||
-> E.SqlExpr (E.Value a)
|
||||
fromMaybe onNothing = maybe onNothing id
|
||||
|
||||
guardMaybe :: PersistField a
|
||||
=> E.SqlExpr (E.Value (Maybe a))
|
||||
-> E.SqlQuery (E.SqlExpr (E.Value a))
|
||||
|
||||
@ -61,7 +61,6 @@ import Data.CaseInsensitive (original, mk)
|
||||
import qualified Data.Text as Text
|
||||
|
||||
import Utils.Form
|
||||
import Utils.Schedule.Types.ScheduleView
|
||||
|
||||
import qualified GHC.Exts (IsList(..))
|
||||
|
||||
@ -504,13 +503,6 @@ instance RenderMessage UniWorX CourseParticipantState where
|
||||
mr :: RenderMessage UniWorX msg => msg -> Text
|
||||
mr = renderMessage foundation ls
|
||||
|
||||
instance RenderMessage UniWorX ScheduleView where
|
||||
renderMessage foundation ls = \case
|
||||
ScheduleViewWeek -> mr MsgScheduleViewWeek
|
||||
where
|
||||
mr :: RenderMessage UniWorX msg => msg -> Text
|
||||
mr = renderMessage foundation ls
|
||||
|
||||
instance RenderMessage UniWorX ExamCloseMode where
|
||||
renderMessage foundation ls = \case
|
||||
ExamCloseSeparate -> mr MsgExamCloseModeSeparate
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
-- SPDX-FileCopyrightText: 2022-2024 Sarah Vaupel <sarah.vaupel@uniworx.de>, Wolfgang Witt <Wolfgang.Witt@campus.lmu.de>
|
||||
-- SPDX-FileCopyrightText: 2022 Wolfgang Witt <Wolfgang.Witt@campus.lmu.de>
|
||||
--
|
||||
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@ -25,7 +25,6 @@ data instance ButtonClass UniWorX
|
||||
| BCDanger
|
||||
| BCLink
|
||||
| BCMassInputAdd | BCMassInputDelete
|
||||
| BCScheduleView | BCScheduleOffset
|
||||
deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic)
|
||||
deriving anyclass (Universe, Finite)
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
-- SPDX-FileCopyrightText: 2022-2024 Sarah Vaupel <sarah.vaupel@uniworx.de>, Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <sarah.vaupel@ifi.lmu.de>,Sarah Vaupel <vaupel.sarah@campus.lmu.de>,Steffen Jost <jost@cip.ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>,Winnie Ros <winnie.ros@campus.lmu.de>
|
||||
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <sarah.vaupel@ifi.lmu.de>,Sarah Vaupel <vaupel.sarah@campus.lmu.de>,Steffen Jost <jost@cip.ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>,Winnie Ros <winnie.ros@campus.lmu.de>
|
||||
--
|
||||
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@ -265,8 +265,6 @@ breadcrumb (CourseR tid ssh csh CLecInviteR) = i18nCrumb MsgBreadcrumbLecturerIn
|
||||
breadcrumb (CourseR tid ssh csh CDeleteR) = i18nCrumb MsgMenuCourseDelete . Just $ CourseR tid ssh csh CShowR
|
||||
breadcrumb (CourseR tid ssh csh CHiWisR) = i18nCrumb MsgBreadcrumbHiWis . Just $ CourseR tid ssh csh CShowR
|
||||
breadcrumb (CourseR tid ssh csh CNotesR) = i18nCrumb MsgBreadcrumbCourseNotes . Just $ CourseR tid ssh csh CShowR
|
||||
breadcrumb (CourseR tid ssh csh (CScheduleOptSetR _opt)) = i18nCrumb MsgBreadcrumbScheduleOpt . Just $ CourseR tid ssh csh CShowR
|
||||
breadcrumb (CourseR tid ssh csh CScheduleOptDelR) = i18nCrumb MsgBreadcrumbScheduleOpt . Just $ CourseR tid ssh csh CShowR
|
||||
|
||||
breadcrumb (CourseR tid ssh csh CNewsNewR) = i18nCrumb MsgMenuCourseNewsNew . Just $ CourseR tid ssh csh CShowR
|
||||
breadcrumb (CourseR tid ssh csh (CourseNewsR cID sRoute)) = case sRoute of
|
||||
@ -278,8 +276,6 @@ breadcrumb (CourseR tid ssh csh (CourseNewsR cID sRoute)) = case sRoute of
|
||||
|
||||
breadcrumb (CourseR tid ssh csh CEventsNewR) = i18nCrumb MsgMenuCourseEventNew . Just $ CourseR tid ssh csh CShowR
|
||||
breadcrumb (CourseR tid ssh csh (CourseEventR _cID sRoute)) = case sRoute of
|
||||
CEvScheduleOptSetR _ -> i18nCrumb MsgBreadcrumbScheduleOpt . Just $ CourseR tid ssh csh CShowR
|
||||
CEvScheduleOptDelR -> i18nCrumb MsgBreadcrumbScheduleOpt . Just $ CourseR tid ssh csh CShowR
|
||||
CEvEditR -> i18nCrumb MsgMenuCourseEventEdit . Just $ CourseR tid ssh csh CShowR
|
||||
CEvDeleteR -> i18nCrumb MsgBreadcrumbCourseEventDelete . Just $ CourseR tid ssh csh CShowR
|
||||
|
||||
@ -300,8 +296,6 @@ breadcrumb (CourseR tid ssh csh (ExamR examn sRoute)) = case sRoute of
|
||||
ERegisterOccR _occn -> i18nCrumb MsgBreadcrumbExamRegister . Just $ CExamR tid ssh csh examn EShowR
|
||||
EAutoOccurrenceR -> i18nCrumb MsgBreadcrumbExamAutoOccurrence . Just $ CExamR tid ssh csh examn EUsersR
|
||||
ECorrectR -> i18nCrumb MsgMenuExamCorrect . Just $ CExamR tid ssh csh examn EShowR
|
||||
EScheduleOptSetR _eoname _opt -> i18nCrumb MsgBreadcrumbScheduleOpt . Just $ CExamR tid ssh csh examn EShowR
|
||||
EScheduleOptDelR _eoname -> i18nCrumb MsgBreadcrumbScheduleOpt . Just $ CExamR tid ssh csh examn EShowR
|
||||
|
||||
breadcrumb (CourseR tid ssh csh (TutorialR tutn sRoute)) = case sRoute of
|
||||
TUsersR -> useRunDB . maybeT (i18nCrumb MsgBreadcrumbTutorial . Just $ CourseR tid ssh csh CTutorialListR) $ do
|
||||
@ -313,8 +307,6 @@ breadcrumb (CourseR tid ssh csh (TutorialR tutn sRoute)) = case sRoute of
|
||||
TCommR -> i18nCrumb MsgMenuTutorialComm . Just $ CTutorialR tid ssh csh tutn TUsersR
|
||||
TRegisterR -> i18nCrumb MsgBreadcrumbTutorialRegister . Just $ CourseR tid ssh csh CShowR
|
||||
TInviteR -> i18nCrumb MsgBreadcrumbTutorInvite . Just $ CTutorialR tid ssh csh tutn TUsersR
|
||||
(TScheduleOptSetR _) -> i18nCrumb MsgBreadcrumbScheduleOpt . Just $ CourseR tid ssh csh CTutorialListR
|
||||
TScheduleOptDelR -> i18nCrumb MsgBreadcrumbScheduleOpt . Just $ CourseR tid ssh csh CTutorialListR
|
||||
|
||||
breadcrumb (CourseR tid ssh csh (SheetR shn sRoute)) = case sRoute of
|
||||
SShowR -> useRunDB . maybeT (i18nCrumb MsgBreadcrumbSheet . Just $ CourseR tid ssh csh SheetListR) $ do
|
||||
@ -377,8 +369,6 @@ breadcrumb (MessageR _) = do
|
||||
breadcrumb MessageListR = i18nCrumb MsgMenuMessageList $ Just AdminR
|
||||
breadcrumb (MessageHideR cID) = i18nCrumb MsgBreadcrumbMessageHide . Just $ MessageR cID
|
||||
|
||||
breadcrumb ScheduleR = i18nCrumb MsgMenuSchedule Nothing
|
||||
|
||||
breadcrumb GlossaryR = i18nCrumb MsgMenuGlossary $ Just InfoR
|
||||
|
||||
breadcrumb EExamListR = i18nCrumb MsgMenuExternalExamList Nothing
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
-- SPDX-FileCopyrightText: 2022-2024 Sarah Vaupel <sarah.vaupel@uniworx.de>, Gregor Kleen <gregor.kleen@ifi.lmu.de>,Steffen Jost <jost@cip.ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>
|
||||
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Steffen Jost <jost@cip.ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>
|
||||
--
|
||||
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@ -303,12 +303,6 @@ decodeUser now UserDefaultConf{..} upsertMode ldapData = do
|
||||
, userPostLastUpdate = Nothing
|
||||
, userPinPassword = Nothing -- must be derived via AVS
|
||||
, userPrefersPostal = userDefaultPrefersPostal
|
||||
, userScheduleView = userDefaultScheduleView
|
||||
, userScheduleWeekDays = userDefaultScheduleWeekDays
|
||||
, userScheduleWeekTimeFrom = userDefaultScheduleWeekTimeFrom
|
||||
, userScheduleWeekTimeTo = userDefaultScheduleWeekTimeTo
|
||||
, userScheduleWeekTimeslotLength = userDefaultScheduleWeekTimeslotLength
|
||||
, userScheduleOccurrenceDisplayDefault = userDefaultScheduleOccurrenceDisplayDefault
|
||||
, ..
|
||||
}
|
||||
userUpdate =
|
||||
|
||||
@ -25,7 +25,6 @@ import Handler.ExamOffice.Course as Handler.Course
|
||||
import Handler.Course.News as Handler.Course
|
||||
import Handler.Course.Events as Handler.Course
|
||||
import Handler.Sheet.PersonalisedFiles as Handler.Course (getCPersonalFilesR)
|
||||
import Handler.Course.Schedule as Handler.Course
|
||||
|
||||
|
||||
getCHiWisR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
|
||||
|
||||
@ -9,4 +9,3 @@ module Handler.Course.Events
|
||||
import Handler.Course.Events.New as Handler.Course.Events
|
||||
import Handler.Course.Events.Edit as Handler.Course.Events
|
||||
import Handler.Course.Events.Delete as Handler.Course.Events
|
||||
import Handler.Course.Events.Schedule as Handler.Course.Events
|
||||
|
||||
@ -1,36 +0,0 @@
|
||||
module Handler.Course.Events.Schedule
|
||||
( getCEvScheduleOptSetR , postCEvScheduleOptSetR
|
||||
, getCEvScheduleOptDelR , postCEvScheduleOptDelR
|
||||
) where
|
||||
|
||||
import Import
|
||||
|
||||
|
||||
getCEvScheduleOptSetR, postCEvScheduleOptSetR :: TermId -> SchoolId -> CourseShorthand -> CryptoUUIDCourseEvent -> Bool -> Handler Html
|
||||
getCEvScheduleOptSetR = postCEvScheduleOptSetR
|
||||
postCEvScheduleOptSetR tid ssh csh ceId opt = do
|
||||
uid <- requireAuthId
|
||||
eId <- decrypt ceId
|
||||
|
||||
runDB $ void $ upsert (CourseEventScheduleOpt
|
||||
{ courseEventScheduleOptCourseEvent = eId
|
||||
, courseEventScheduleOptUser = uid
|
||||
, courseEventScheduleOptOpt = opt
|
||||
})
|
||||
[ CourseEventScheduleOptOpt =. opt
|
||||
]
|
||||
|
||||
addMessageI Success $ bool MsgScheduleOptOutSuccess MsgScheduleOptInSuccess opt
|
||||
redirect $ CourseR tid ssh csh CShowR
|
||||
|
||||
|
||||
getCEvScheduleOptDelR, postCEvScheduleOptDelR :: TermId -> SchoolId -> CourseShorthand -> CryptoUUIDCourseEvent -> Handler Html
|
||||
getCEvScheduleOptDelR = postCEvScheduleOptDelR
|
||||
postCEvScheduleOptDelR tid ssh csh ceId = do
|
||||
uid <- requireAuthId
|
||||
eId <- decrypt ceId
|
||||
|
||||
runDB $ deleteBy (UniqueCourseEventScheduleOpt eId uid)
|
||||
|
||||
addMessageI Success MsgScheduleOptDeleteSuccess
|
||||
redirect $ CourseR tid ssh csh CShowR
|
||||
@ -1,41 +0,0 @@
|
||||
module Handler.Course.Schedule
|
||||
( getCScheduleOptSetR, postCScheduleOptSetR
|
||||
, getCScheduleOptDelR, postCScheduleOptDelR
|
||||
) where
|
||||
|
||||
import Import
|
||||
|
||||
|
||||
getCScheduleOptSetR, postCScheduleOptSetR :: TermId -> SchoolId -> CourseShorthand -> Bool -> Handler Html
|
||||
getCScheduleOptSetR = postCScheduleOptSetR
|
||||
postCScheduleOptSetR tid ssh csh opt = do
|
||||
uid <- requireAuthId
|
||||
|
||||
mResult <- runDB $ maybeT (return Nothing) $ do
|
||||
cid <- MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
|
||||
scheduleOpt <- lift $ upsert (CourseScheduleOpt
|
||||
{ courseScheduleOptCourse = cid
|
||||
, courseScheduleOptUser = uid
|
||||
, courseScheduleOptOpt = opt
|
||||
})
|
||||
[ CourseScheduleOptOpt =. opt
|
||||
]
|
||||
return $ Just scheduleOpt
|
||||
|
||||
case mResult of
|
||||
Just (Entity _ CourseScheduleOpt{..}) -> addMessageI Success $ bool MsgCourseScheduleOptOutSuccess MsgCourseScheduleOptInSuccess courseScheduleOptOpt
|
||||
Nothing -> addMessageI Error MsgCourseScheduleOptError
|
||||
redirect $ CourseR tid ssh csh CShowR
|
||||
|
||||
|
||||
getCScheduleOptDelR, postCScheduleOptDelR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
|
||||
getCScheduleOptDelR = postCScheduleOptDelR
|
||||
postCScheduleOptDelR tid ssh csh = do
|
||||
uid <- requireAuthId
|
||||
|
||||
runDB $ maybeT (return ()) $ do
|
||||
cid <- MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
|
||||
lift . deleteBy $ UniqueCourseScheduleOpt cid uid
|
||||
|
||||
addMessageI Success MsgCourseScheduleOptDeleteSuccess
|
||||
redirect $ CourseR tid ssh csh CShowR
|
||||
@ -10,7 +10,6 @@ import Import
|
||||
|
||||
import Utils.Course
|
||||
import Utils.Form
|
||||
import Utils.Schedule
|
||||
import Handler.Utils
|
||||
import Handler.Utils.Course
|
||||
import Handler.Utils.Tutorial
|
||||
@ -30,16 +29,14 @@ import Handler.Exam.List (mkExamTable)
|
||||
|
||||
getCShowR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
|
||||
getCShowR tid ssh csh = do
|
||||
mbAuth <- maybeAuthPair
|
||||
mbAid <- maybeAuthId
|
||||
now <- liftIO getCurrentTime
|
||||
ata <- getSessionActiveAuthTags
|
||||
|
||||
(cid,course,courseVisible,schoolName,participants,registration,lecturers,assistants,administrators,correctors,tutors,news,events,submissionGroup,_mayReRegister,mCourseScheduleOpt,mayReRegister,(mayViewSheets,mayViewAnySheet),(mayViewMaterials,mayViewAnyMaterial),courseQualifications) <- runDB . maybeT notFound $ do
|
||||
(cid,course,courseVisible,schoolName,participants,registration,lecturers,assistants,administrators,correctors,tutors,news,events,submissionGroup,_mayReRegister,(mayViewSheets, mayViewAnySheet), (mayViewMaterials, mayViewAnyMaterial),courseQualifications) <- runDB . maybeT notFound $ do
|
||||
[(E.Entity cid course, E.Value courseVisible, E.Value schoolName, E.Value participants, fmap entityVal -> registration)]
|
||||
<- lift . E.select . E.from $
|
||||
\((school `E.InnerJoin` course) `E.LeftOuterJoin` participant) -> do
|
||||
E.on $ E.just (course E.^. CourseId) E.==. participant E.?. CourseParticipantCourse
|
||||
E.&&. E.val (fst <$> mbAuth) E.==. participant E.?. CourseParticipantUser
|
||||
E.&&. E.val mbAid E.==. participant E.?. CourseParticipantUser
|
||||
E.&&. participant E.?. CourseParticipantState E.==. E.just (E.val CourseParticipantActive)
|
||||
E.on $ course E.^. CourseSchool E.==. school E.^. SchoolId
|
||||
E.where_ $ course E.^. CourseTerm E.==. E.val tid
|
||||
@ -99,30 +96,17 @@ getCShowR tid ssh csh = do
|
||||
|
||||
return (cID, n, visible, files, lastEditText, mayEditNews, mayDelete, archiveUrl)
|
||||
|
||||
mCourseScheduleOpt <- case mbAuth of
|
||||
Just (uid,_) -> lift $ getBy $ UniqueCourseScheduleOpt cid uid
|
||||
Nothing -> return Nothing
|
||||
|
||||
events' <- fmap (sortOn $ courseEventTime . entityVal . view _1) . lift . E.select . E.from $ \courseEvent -> do
|
||||
E.where_ $ courseEvent E.^. CourseEventCourse E.==. E.val cid
|
||||
let showRoom = maybe E.false (flip showCourseEventRoom courseEvent . E.val . view _1) mbAuth
|
||||
let showRoom = maybe E.false (flip showCourseEventRoom courseEvent . E.val) mbAid
|
||||
E.||. E.not_ (courseEvent E.^. CourseEventRoomHidden)
|
||||
return (courseEvent, showRoom)
|
||||
events <- forM events' $ \(Entity evId ev, E.Value showRoom) -> do
|
||||
evId' <- encrypt evId
|
||||
shouldBeDisplayedInSchedule <- lift $ E.selectExists . E.from $ \(c `E.InnerJoin` cEv) -> do
|
||||
E.on $ c E.^. CourseId E.==. cEv E.^. CourseEventCourse
|
||||
E.where_ $ cEv E.^. CourseEventId E.==. E.val evId
|
||||
E.&&. courseEventShouldBeDisplayedInSchedule (view _1 <$> mbAuth) ata c cEv
|
||||
mCourseEventScheduleOpt <- case mbAuth of
|
||||
Just (aid,_) -> lift $ getBy $ UniqueCourseEventScheduleOpt evId aid
|
||||
Nothing -> return Nothing
|
||||
return (evId', ev, showRoom, shouldBeDisplayedInSchedule, mCourseEventScheduleOpt)
|
||||
events <- mapM (\(Entity evId ev, E.Value showRoom) -> (, ev, showRoom) <$> encrypt evId) events'
|
||||
|
||||
hasSubmissionGroups <- lift . E.selectExists . E.from $ \(submissionGroupUser `E.InnerJoin` submissionGroup) -> do
|
||||
E.on $ submissionGroupUser E.^. SubmissionGroupUserSubmissionGroup E.==. submissionGroup E.^. SubmissionGroupId
|
||||
E.where_ $ submissionGroup E.^. SubmissionGroupCourse E.==. E.val cid
|
||||
submissionGroup' <- lift . for mbAuth $ \(uid,_) ->
|
||||
submissionGroup' <- lift . for mbAid $ \uid ->
|
||||
fmap (listToMaybe . fmap E.unValue) . E.select . E.from $ \(submissionGroupUser `E.InnerJoin` submissionGroup) -> do
|
||||
E.on $ submissionGroupUser E.^. SubmissionGroupUserSubmissionGroup E.==. submissionGroup E.^. SubmissionGroupId
|
||||
E.where_ $ submissionGroup E.^. SubmissionGroupCourse E.==. E.val cid
|
||||
@ -144,14 +128,14 @@ getCShowR tid ssh csh = do
|
||||
return $ material E.^. MaterialName
|
||||
mayViewAnyMaterial <- lift . anyM materials $ \(E.Value mnm) -> hasReadAccessTo $ CMaterialR tid ssh csh mnm MShowR
|
||||
|
||||
courseQualifications <- lift $ getCourseQualifications cID
|
||||
courseQualifications <- lift $ getCourseQualifications cid
|
||||
|
||||
return (cid,course,courseVisible,schoolName,participants,registration,lecturers,assistants,administrators,correctors,tutors,news,events,submissionGroup,mayReRegister,mCourseScheduleOpt,mayReRegister,(mayViewSheets,mayViewAnySheet),(mayViewMaterials,mayViewAnyMaterial),courseQualifications)
|
||||
return (cid,course,courseVisible,schoolName,participants,registration,lecturers,assistants,administrators,correctors,tutors,news,events,submissionGroup,mayReRegister, (mayViewSheets, mayViewAnySheet), (mayViewMaterials, mayViewAnyMaterial), courseQualifications)
|
||||
|
||||
mDereg <- traverse (formatTime SelFormatDateTime) $ courseDeregisterUntil course
|
||||
|
||||
regForm <- if
|
||||
| is _Just mbAuth -> do
|
||||
| is _Just mbAid -> do
|
||||
(courseRegisterForm', regButton) <- courseRegisterForm (Entity cid course)
|
||||
(regWidget, regEnctype) <- generateFormPost $ renderAForm FormStandard courseRegisterForm'
|
||||
return $ wrapForm' regButton regWidget def
|
||||
@ -175,7 +159,7 @@ getCShowR tid ssh csh = do
|
||||
|
||||
dbtSQLQuery tutorial = do
|
||||
E.where_ $ tutorial E.^. TutorialCourse E.==. E.val cid
|
||||
let showRoom = maybe E.false (flip showTutorialRoom tutorial . E.val . view _1) mbAuth
|
||||
let showRoom = maybe E.false (flip showTutorialRoom tutorial . E.val) mbAid
|
||||
E.||. E.not_ (tutorial E.^. TutorialRoomHidden)
|
||||
return (tutorial, showRoom)
|
||||
dbtRowKey = (E.^. TutorialId)
|
||||
@ -213,49 +197,22 @@ getCShowR tid ssh csh = do
|
||||
in return $ E.val tutorialCapacity' E.-. numParticipants
|
||||
return . toWidget $ tshow freeCapacity
|
||||
, guardMonoid (not mayMassRegister || isJust registration) $
|
||||
sortable Nothing (mempty & cellAttrs <>~ pure ("uw-hide-columns--hider-label", mr MsgActionsHead)) $ \(view resultTutorial -> Entity tutId Tutorial{..}) -> sqlCell $ do
|
||||
mayRegister <- (== Authorized) <$> evalAccessDB (CTutorialR tid ssh csh tutorialName TRegisterR) True
|
||||
isRegistered <- case mbAuth of
|
||||
Nothing -> return False
|
||||
Just (uid,_) -> existsBy $ UniqueTutorialParticipant tutId uid
|
||||
tutRegister <- if
|
||||
| mayRegister -> do
|
||||
(tutRegisterForm, tutRegisterEnctype) <- liftHandler . generateFormPost . buttonForm' $ bool [BtnRegister] [BtnDeregister] isRegistered
|
||||
return $ wrapForm tutRegisterForm def
|
||||
{ formAction = Just . SomeRoute $ CTutorialR tid ssh csh tutorialName TRegisterR
|
||||
, formEncoding = tutRegisterEnctype
|
||||
, formSubmit = FormNoSubmit
|
||||
}
|
||||
| isRegistered -> return [whamlet|_{MsgTutorialRegistered}|]
|
||||
| otherwise -> return mempty
|
||||
tutScheduleOptSet <- if
|
||||
| Just (uid,_) <- mbAuth -> do
|
||||
shouldBeDisplayedInSchedule <- E.selectExists . E.from $ \(c `E.InnerJoin` tut) -> do
|
||||
E.on $ c E.^. CourseId E.==. tut E.^. TutorialCourse
|
||||
E.where_ $ tut E.^. TutorialId E.==. E.val tutId
|
||||
E.&&. tutorialShouldBeDisplayedInSchedule (Just uid) ata c tut
|
||||
(tutScheduleForm, tutScheduleEnctype) <- liftHandler . generateFormPost . buttonForm' $ bool [BtnScheduleOptIn] [BtnScheduleOptOut] shouldBeDisplayedInSchedule
|
||||
return $ wrapForm tutScheduleForm def
|
||||
{ formAction = Just . SomeRoute $ CTutorialR tid ssh csh tutorialName (TScheduleOptSetR $ not shouldBeDisplayedInSchedule)
|
||||
, formEncoding = tutScheduleEnctype
|
||||
, formSubmit = FormNoSubmit
|
||||
}
|
||||
| otherwise -> return mempty
|
||||
tutScheduleOptDel <- if
|
||||
| Just (uid,_) <- mbAuth -> do
|
||||
mScheduleOpt <- getBy $ UniqueTutorialScheduleOpt tutId uid
|
||||
if is _Just mScheduleOpt
|
||||
then do
|
||||
(tutScheduleOptDelForm, tutScheduleOptDelEnctype) <- liftHandler . generateFormPost . buttonForm' $ [BtnScheduleOptDel]
|
||||
return $ wrapForm tutScheduleOptDelForm def
|
||||
{ formAction = Just . SomeRoute $ CTutorialR tid ssh csh tutorialName TScheduleOptDelR
|
||||
, formEncoding = tutScheduleOptDelEnctype
|
||||
, formSubmit = FormNoSubmit
|
||||
}
|
||||
else return mempty
|
||||
| otherwise -> return mempty
|
||||
return $ tutRegister <> tutScheduleOptSet <> tutScheduleOptDel
|
||||
, guardMonoid mayMassRegister $ -- TODO: schedule opts?
|
||||
sortable Nothing (mempty & cellAttrs <>~ pure ("uw-hide-columns--hider-label", mr MsgTableActionsHead)) $ \(view resultTutorial -> Entity tutId Tutorial{..}) -> sqlCell $ do
|
||||
mayRegister <- (== Authorized) <$> evalAccessDB (CTutorialR tid ssh csh tutorialName TRegisterR) True
|
||||
isRegistered <- case mbAid of
|
||||
Nothing -> return False
|
||||
Just uid -> existsBy $ UniqueTutorialParticipant tutId uid
|
||||
if
|
||||
| mayRegister -> do
|
||||
(tutRegisterForm, tutRegisterEnctype) <- liftHandler . generateFormPost . buttonForm' $ bool [BtnRegister] [BtnDeregister] isRegistered
|
||||
return $ wrapForm tutRegisterForm def
|
||||
{ formAction = Just . SomeRoute $ CTutorialR tid ssh csh tutorialName TRegisterR
|
||||
, formEncoding = tutRegisterEnctype
|
||||
, formSubmit = FormNoSubmit
|
||||
}
|
||||
| isRegistered -> return [whamlet|_{MsgTutorialRegistered}|]
|
||||
| otherwise -> return mempty
|
||||
, guardMonoid mayMassRegister $
|
||||
sortable Nothing (mempty & cellAttrs <>~ pure ("uw-hide-columns--hider-label", mr MsgTableActionsHead)) $ \(view $ resultTutorial . _entityVal -> Tutorial{..}) ->
|
||||
cell $ linkButton mempty (msg2widget MsgMassRegister) [BCIsButton, BCPrimary] (SomeRoute $ CTutorialR tid ssh csh tutorialName TAddUserR)
|
||||
]
|
||||
@ -296,19 +253,12 @@ getCShowR tid ssh csh = do
|
||||
, length fs <= 3
|
||||
, all (views (_1 . _2) $ notElem pathSeparator) fs
|
||||
]
|
||||
hiddenEventNotes = all (\(_,CourseEvent{..},_,_,_) -> is _Nothing courseEventNote) events
|
||||
hiddenEventNotes = all (\(_,CourseEvent{..},_) -> is _Nothing courseEventNote) events
|
||||
Course{courseVisibleFrom,courseVisibleTo} = course
|
||||
mayCreateNews <- hasWriteAccessTo $ CourseR tid ssh csh CNewsNewR
|
||||
mayCreateEvents <- hasWriteAccessTo $ CourseR tid ssh csh CEventsNewR
|
||||
mayEdit <- hasWriteAccessTo $ CourseR tid ssh csh CEditR
|
||||
|
||||
let courseScheduleOptToggleValue User{userScheduleOccurrenceDisplayDefault} = maybe
|
||||
( userScheduleOccurrenceDisplayDefault
|
||||
&& ( is _Just registration )
|
||||
)
|
||||
(courseScheduleOptOpt . entityVal)
|
||||
mCourseScheduleOpt
|
||||
|
||||
let heading = [whamlet|
|
||||
$newline never
|
||||
^{courseName course}
|
||||
|
||||
@ -13,7 +13,6 @@ import Handler.Exam.RegistrationInvite as Handler.Exam
|
||||
import Handler.Exam.New as Handler.Exam
|
||||
import Handler.Exam.Edit as Handler.Exam
|
||||
import Handler.Exam.Show as Handler.Exam
|
||||
import Handler.Exam.Schedule as Handler.Exam
|
||||
import Handler.Exam.Users as Handler.Exam
|
||||
import Handler.Exam.AddUser as Handler.Exam
|
||||
import Handler.Exam.AutoOccurrence as Handler.Exam
|
||||
|
||||
@ -1,45 +0,0 @@
|
||||
module Handler.Exam.Schedule
|
||||
( getEScheduleOptSetR, postEScheduleOptSetR
|
||||
, getEScheduleOptDelR, postEScheduleOptDelR
|
||||
) where
|
||||
|
||||
import Import
|
||||
|
||||
import Handler.Utils.Exam
|
||||
|
||||
|
||||
getEScheduleOptSetR, postEScheduleOptSetR :: TermId -> SchoolId -> CourseShorthand -> ExamName -> ExamOccurrenceName -> Bool -> Handler Html
|
||||
getEScheduleOptSetR = postEScheduleOptSetR
|
||||
postEScheduleOptSetR tid ssh csh examn eoccn opt = do
|
||||
uid <- requireAuthId
|
||||
|
||||
mResult <- runDB $ maybeT (return Nothing) $ do
|
||||
eId <- lift $ fetchExamId tid ssh csh examn
|
||||
eoId <- MaybeT . getKeyBy $ UniqueExamOccurrence eId eoccn
|
||||
scheduleOpt <- lift $ upsert ExamOccurrenceScheduleOpt
|
||||
{ examOccurrenceScheduleOptExamOccurrence = eoId
|
||||
, examOccurrenceScheduleOptUser = uid
|
||||
, examOccurrenceScheduleOptOpt = opt
|
||||
}
|
||||
[ ExamOccurrenceScheduleOptOpt =. opt
|
||||
]
|
||||
return $ Just scheduleOpt
|
||||
|
||||
case mResult of
|
||||
Just (Entity _ ExamOccurrenceScheduleOpt{..}) -> addMessageI Success $ bool MsgScheduleOptOutSuccess MsgScheduleOptInSuccess examOccurrenceScheduleOptOpt
|
||||
Nothing -> addMessageI Error MsgScheduleOptError
|
||||
redirect $ CExamR tid ssh csh examn EShowR
|
||||
|
||||
|
||||
getEScheduleOptDelR, postEScheduleOptDelR :: TermId -> SchoolId -> CourseShorthand -> ExamName -> ExamOccurrenceName -> Handler Html
|
||||
getEScheduleOptDelR = postEScheduleOptDelR
|
||||
postEScheduleOptDelR tid ssh csh examn eoccn = do
|
||||
uid <- requireAuthId
|
||||
|
||||
runDB $ maybeT (return ()) $ do
|
||||
eId <- lift $ fetchExamId tid ssh csh examn
|
||||
eoId <- MaybeT . getKeyBy $ UniqueExamOccurrence eId eoccn
|
||||
lift . deleteBy $ UniqueExamOccurrenceScheduleOpt eoId uid
|
||||
|
||||
addMessageI Success MsgScheduleOptDeleteSuccess
|
||||
redirect $ CExamR tid ssh csh examn EShowR
|
||||
@ -24,14 +24,11 @@ import qualified Data.CaseInsensitive as CI
|
||||
import Handler.Utils
|
||||
import Handler.Utils.Exam
|
||||
|
||||
import Utils.Schedule
|
||||
|
||||
|
||||
getEShowR :: TermId -> SchoolId -> CourseShorthand -> ExamName -> Handler Html
|
||||
getEShowR tid ssh csh examn = do
|
||||
cTime <- liftIO getCurrentTime
|
||||
mAuth <- maybeAuth
|
||||
ata <- getSessionActiveAuthTags
|
||||
mUid <- maybeAuthId
|
||||
|
||||
(Entity eId Exam{..}, School{..}, examParts, examVisible, (gradingVisible, gradingShown), (occurrenceAssignmentsVisible, occurrenceAssignmentsShown), results, result, bonus, occurrences, (registered, mayRegister), registeredCount, lecturerInfoShown, staffInfoShown, extraSchools, (partsVisible, partsShown)) <- runDB $ do
|
||||
exam@(Entity eId Exam{..}) <- fetchExam tid ssh csh examn
|
||||
@ -57,27 +54,21 @@ getEShowR tid ssh csh examn = do
|
||||
flip filterM sheets' $ \(Entity _ Sheet{..}) -> hasReadAccessTo $ CSheetR tid ssh csh sheetName SShowR
|
||||
examParts <- fmap (sortOn . view $ _1 . _entityVal . _examPartNumber) $ selectList [ ExamPartExam ==. eId ] [ Asc ExamPartName ] >>= traverse (\ep@(Entity epId _) -> (ep,,) <$> encrypt @ExamPartId @UUID epId <*> examPartSheets epId)
|
||||
|
||||
resultsRaw <- for mAuth $ \(Entity uid _) -> E.select . E.from $ \examPartResult -> do
|
||||
resultsRaw <- for mUid $ \uid ->
|
||||
E.select . E.from $ \examPartResult -> do
|
||||
E.where_ $ examPartResult E.^. ExamPartResultUser E.==. E.val uid
|
||||
E.&&. examPartResult E.^. ExamPartResultExamPart `E.in_` E.valList (map (views _1 entityKey) examParts)
|
||||
return examPartResult
|
||||
let results = maybe Map.empty (\rs -> Map.fromList [ (examPartResultExamPart, res) | res@(Entity _ ExamPartResult{..}) <- rs ]) resultsRaw
|
||||
|
||||
result <- fmap join . for mAuth $ getBy . UniqueExamResult eId . entityKey
|
||||
bonus <- fmap join . for mAuth $ getBy . UniqueExamBonus eId . entityKey
|
||||
result <- fmap join . for mUid $ getBy . UniqueExamResult eId
|
||||
bonus <- fmap join . for mUid $ getBy . UniqueExamBonus eId
|
||||
|
||||
occurrencesRaw <- E.select . E.from $ \(course `E.InnerJoin` ex `E.InnerJoin` (examOccurrence `E.LeftOuterJoin` examOccurrenceScheduleOpt)) -> do
|
||||
E.on $ course E.^. CourseId E.==. ex E.^. ExamCourse
|
||||
E.on $ ex E.^. ExamId E.==. examOccurrence E.^. ExamOccurrenceExam
|
||||
E.on $ E.just (examOccurrence E.^. ExamOccurrenceId) E.==. examOccurrenceScheduleOpt E.?. ExamOccurrenceScheduleOptExamOccurrence
|
||||
occurrencesRaw <- E.select . E.from $ \examOccurrence -> do
|
||||
E.where_ $ examOccurrence E.^. ExamOccurrenceExam E.==. E.val eId
|
||||
-- TODO: works for now, but can possibly be simplified
|
||||
E.&&. maybe E.true (\(Entity uid _) -> E.isNothing (examOccurrenceScheduleOpt E.?. ExamOccurrenceScheduleOptUser) E.||. examOccurrenceScheduleOpt E.?. ExamOccurrenceScheduleOptUser E.==. E.just (E.val uid)) mAuth
|
||||
|
||||
let
|
||||
shouldBeDisplayedInSchedule = examOccurrenceShouldBeDisplayedInSchedule (entityKey <$> mAuth) ata cTime course ex examOccurrence
|
||||
registered
|
||||
| Just (Entity uid _) <- mAuth
|
||||
| Just uid <- mUid
|
||||
= E.exists . E.from $ \examRegistration ->
|
||||
E.where_ $ examRegistration E.^. ExamRegistrationExam E.==. E.val eId
|
||||
E.&&. examRegistration E.^. ExamRegistrationUser E.==. E.val uid
|
||||
@ -88,22 +79,22 @@ getEShowR tid ssh csh examn = do
|
||||
= E.subSelectCount . E.from $ \examRegistration ->
|
||||
E.where_ $ examRegistration E.^. ExamRegistrationExam E.==. E.val eId
|
||||
E.&&. examRegistration E.^. ExamRegistrationOccurrence E.==. E.just (examOccurrence E.^. ExamOccurrenceId)
|
||||
showRoom = maybe E.false (flip showExamOccurrenceRoom examOccurrence . E.val . entityKey) mAuth
|
||||
showRoom = maybe E.false (flip showExamOccurrenceRoom examOccurrence . E.val) mUid
|
||||
E.||. E.not_ (examOccurrence E.^. ExamOccurrenceRoomHidden)
|
||||
E.orderBy [E.desc registered, E.asc $ examOccurrence E.^. ExamOccurrenceStart, E.asc $ examOccurrence E.^. ExamOccurrenceRoom]
|
||||
return (examOccurrence, registered, registeredCount, showRoom, shouldBeDisplayedInSchedule, examOccurrenceScheduleOpt)
|
||||
return (examOccurrence, registered, registeredCount, showRoom)
|
||||
|
||||
registeredCount <- fromIntegral <$> count [ ExamRegistrationExam ==. eId ]
|
||||
|
||||
registered <- for mAuth $ getBy . UniqueExamRegistration eId . entityKey
|
||||
registered <- for mUid $ getBy . UniqueExamRegistration eId
|
||||
mayRegister <- if
|
||||
| examOccurrenceRule == ExamRoomFifo -> anyM occurrencesRaw $ \(Entity _ ExamOccurrence{..}, _, _, _, _, _) ->
|
||||
| examOccurrenceRule == ExamRoomFifo -> anyM occurrencesRaw $ \(Entity _ ExamOccurrence{..}, _, _, _) ->
|
||||
hasWriteAccessTo . CExamR tid ssh csh examName $ ERegisterOccR examOccurrenceName
|
||||
| otherwise -> hasWriteAccessTo $ CExamR tid ssh csh examName ERegisterR
|
||||
|
||||
let occurrences = sortOn sortPred $ map (over _5 E.unValue . over _4 E.unValue . over _3 E.unValue . over _2 E.unValue) occurrencesRaw
|
||||
let occurrences = sortOn sortPred $ map (over _4 E.unValue . over _3 E.unValue . over _2 E.unValue) occurrencesRaw
|
||||
where
|
||||
sortPred (Entity _ ExamOccurrence{..}, registered', _, showRoom, _, _)
|
||||
sortPred (Entity _ ExamOccurrence{..}, registered', _, showRoom)
|
||||
= (Down $ registered' && not mayRegister, examOccurrenceStart, guardOn @Maybe showRoom examOccurrenceRoom)
|
||||
|
||||
staffInfoShown <- hasReadAccessTo $ CExamR tid ssh csh examn EGradesR
|
||||
@ -142,13 +133,13 @@ getEShowR tid ssh csh examn = do
|
||||
mayRegister' <- fmap ((Map.!) . Map.fromList) . for (Nothing : map Just occurrences) $ \case
|
||||
Nothing ->
|
||||
fmap (Nothing, ) . hasWriteAccessTo $ CExamR tid ssh csh examName ERegisterR
|
||||
Just (Entity occId ExamOccurrence{..}, _, _, _, _, _) ->
|
||||
Just (Entity occId ExamOccurrence{..}, _, _, _) ->
|
||||
fmap (Just occId, ) . hasWriteAccessTo . CExamR tid ssh csh examName $ ERegisterOccR examOccurrenceName
|
||||
|
||||
let examTimes = all (\(Entity _ ExamOccurrence{..}, _, _, _, _, _) -> Just examOccurrenceStart == examStart && examOccurrenceEnd == examEnd) occurrences
|
||||
let examTimes = all (\(Entity _ ExamOccurrence{..}, _, _, _) -> Just examOccurrenceStart == examStart && examOccurrenceEnd == examEnd) occurrences
|
||||
examRoom = do
|
||||
(Entity _ primeOcc, _, _, _, _, _) <- occurrences ^? _head
|
||||
guard $ all (\(Entity _ occ, _, _, _, _, _) -> examOccurrenceRoom occ == examOccurrenceRoom primeOcc) occurrences
|
||||
(Entity _ primeOcc, _, _, _) <- occurrences ^? _head
|
||||
guard $ all (\(Entity _ occ, _, _, _) -> examOccurrenceRoom occ == examOccurrenceRoom primeOcc) occurrences
|
||||
guard $ andOf (folded . _4) occurrences
|
||||
examOccurrenceRoom primeOcc
|
||||
registerWidget mOcc
|
||||
|
||||
@ -24,27 +24,22 @@ import qualified Data.Conduit.Lift as C
|
||||
|
||||
import qualified Data.HashMap.Strict as HashMap
|
||||
|
||||
-- TODO: deprecated
|
||||
import Utils.Schedule.Types (ScheduleOffset(..))
|
||||
import Utils.Schedule.Week
|
||||
|
||||
import Handler.Utils.Exam (showExamOccurrenceRoom)
|
||||
|
||||
|
||||
getNewsR :: Handler Html
|
||||
getNewsR = do
|
||||
mUser <- maybeAuth
|
||||
muid <- maybeAuthId
|
||||
defaultLayout $ do
|
||||
setTitleI MsgNewsHeading
|
||||
|
||||
newsSystemMessages
|
||||
|
||||
when (is _Nothing mUser) $
|
||||
when (is _Nothing muid) $
|
||||
notificationWidget NotificationBroad Info $(i18nWidgetFile "pitch")
|
||||
|
||||
case mUser of
|
||||
Just user@(Entity uid _) -> do
|
||||
newsSchedule user
|
||||
case muid of
|
||||
Just uid -> do
|
||||
newsUpcomingExams uid
|
||||
newsUpcomingSheets uid
|
||||
Nothing ->
|
||||
@ -98,14 +93,6 @@ newsSystemMessages = do
|
||||
$(widgetFile "news/system-messages")
|
||||
|
||||
|
||||
-- TODO: deprecated; update once ScheduleR is finished
|
||||
newsSchedule :: Entity User -> Widget
|
||||
newsSchedule user = do
|
||||
now <- liftIO getCurrentTime
|
||||
let schedule = weekSchedule now user ScheduleOffsetNone
|
||||
$(widgetFile "news/schedule")
|
||||
|
||||
|
||||
newsUpcomingSheets :: UserId -> Widget
|
||||
newsUpcomingSheets uid = do
|
||||
cTime <- liftIO getCurrentTime
|
||||
|
||||
@ -44,11 +44,8 @@ import qualified Data.CaseInsensitive as CI
|
||||
|
||||
import Jobs
|
||||
|
||||
import Foundation.I18n ()
|
||||
import Foundation.Yesod.Auth (updateUserLanguage)
|
||||
|
||||
import Utils.Schedule.Types.ScheduleView
|
||||
|
||||
|
||||
{-# ANN module ("HLint: ignore Functor law" :: String) #-}
|
||||
|
||||
@ -70,19 +67,12 @@ type EOLabels = Map (Either ExamOfficeLabelName ExamOfficeLabelId) EOLabelData
|
||||
data SettingsForm = SettingsForm
|
||||
{ stgDisplayName :: UserDisplayName
|
||||
, stgDisplayEmail :: UserEmail
|
||||
, stgScheduleView :: ScheduleView
|
||||
, stgScheduleWeekDays :: ScheduleWeekDays
|
||||
, stgScheduleWeekTimeFrom
|
||||
, stgScheduleWeekTimeTo :: NominalDiffTime
|
||||
, stgScheduleWeekTimeslotLength :: NominalDiffTime
|
||||
, stgScheduleOccurrenceDisplayDefault :: Bool
|
||||
, stgMaxFavourites :: Int
|
||||
, stgMaxFavouriteTerms :: Int
|
||||
, stgTheme :: Theme
|
||||
, stgDateTime :: DateTimeFormat
|
||||
, stgDate :: DateTimeFormat
|
||||
, stgTime :: DateTimeFormat
|
||||
, stgWeekStart :: DayOfWeek
|
||||
, stgDownloadFiles :: Bool
|
||||
, stgWarningDays :: NominalDiffTime
|
||||
, stgShowSex :: Bool
|
||||
@ -132,21 +122,8 @@ makeSettingForm template html = do
|
||||
-- isAdmin <- checkAdmin
|
||||
(result, widget) <- flip (renderAForm FormStandard) html $ SettingsForm
|
||||
<$ aformSection MsgFormPersonalAppearance
|
||||
<*> areq (textField & cfStrip) (fslI MsgUserDisplayName & setTooltip MsgUserDisplayNameRulesBelow) (stgDisplayName <$> template)
|
||||
<*> areq (textField & cfStrip) (fslI MsgUserDisplayName & setTooltip MsgUserDisplayNameRulesBelow) (stgDisplayName <$> template)
|
||||
<*> areq (textField & cfStrip & cfCI) (fslI MsgUserDisplayEmail & setTooltip MsgUserDisplayEmailTip) (stgDisplayEmail <$> template)
|
||||
<* aformSection MsgSchedule
|
||||
<*> (case universeF of
|
||||
[sOpt] -> pure sOpt -- Don't bother showing the select as long as there is only one option
|
||||
_other -> apopt (selectField optionsFinite) (fslI MsgProfileScheduleView & setTooltip MsgProfileScheduleViewTip) { fsId = Just "schedule-view-select" } (stgScheduleView <$> template)
|
||||
)
|
||||
<*> scheduleWeekDaysForm (stgScheduleWeekDays <$> template)
|
||||
<*> areq timeOfDayField
|
||||
(fslpI MsgScheduleWeekTimeFrom (mr MsgScheduleWeekTimeFromPlaceholder) & setTooltip MsgScheduleWeekTimeFromTip) (stgScheduleWeekTimeFrom <$> template)
|
||||
<*> areq timeOfDayField
|
||||
(fslpI MsgScheduleWeekTimeTo (mr MsgScheduleWeekTimeToPlaceholder ) & setTooltip MsgScheduleWeekTimeToTip ) (stgScheduleWeekTimeTo <$> template)
|
||||
<*> areq (convertField (fromInteger . (* 60)) ((`quot` 60) . round) $ posIntFieldI MsgScheduleWeekTimeslotLength)
|
||||
(fslpI MsgScheduleWeekTimeslotLength (mr MsgScheduleWeekTimeslotLengthPlaceholder) & setTooltip MsgScheduleWeekTimeslotLengthTip) (stgScheduleWeekTimeslotLength <$> template)
|
||||
<*> apopt checkBoxField (fslI MsgScheduleOccurrenceDisplayDefault & setTooltip MsgScheduleOccurrenceDisplayDefaultTip) (stgScheduleOccurrenceDisplayDefault <$> template)
|
||||
<* aformSection MsgFormCosmetics
|
||||
<*> areq (natFieldI MsgFavouritesNotNatural)
|
||||
(fslpI MsgFavourites (mr MsgFavouritesPlaceholder) & setTooltip MsgFavouritesTip) (stgMaxFavourites <$> template)
|
||||
@ -157,7 +134,6 @@ makeSettingForm template html = do
|
||||
<*> areq (selectField $ dateTimeFormatOptions SelFormatDateTime) (fslI MsgDateTimeFormat) (stgDateTime <$> template)
|
||||
<*> areq (selectField $ dateTimeFormatOptions SelFormatDate) (fslI MsgDateFormat) (stgDate <$> template)
|
||||
<*> areq (selectField $ dateTimeFormatOptions SelFormatTime) (fslI MsgTimeFormat) (stgTime <$> template)
|
||||
<*> areq (selectField optionsFinite) (fslI MsgWeekStart) (stgWeekStart <$> template)
|
||||
<* aformSection MsgFormBehaviour
|
||||
<*> apopt checkBoxField (fslI MsgDownloadFiles
|
||||
& setTooltip MsgDownloadFilesTip
|
||||
@ -180,12 +156,7 @@ makeSettingForm template html = do
|
||||
<*> notificationForm (stgNotificationSettings <$> template)
|
||||
return (result, widget) -- no validation here, done later by validateSettings
|
||||
where
|
||||
themeList = [ Option (toMessage t) t (toPathPiece t) | t <- universeF ]
|
||||
|
||||
scheduleWeekDaysForm :: Maybe ScheduleWeekDays -> AForm Handler ScheduleWeekDays
|
||||
scheduleWeekDaysForm template' = prismAForm (_Wrapped . _IndicatorFunction) template' $ \template
|
||||
-> let dayForm wDay = apopt checkBoxField (fslI wDay) (template <&> ($ wDay))
|
||||
in funcForm dayForm (fslI MsgScheduleWeekDays & setTooltip MsgScheduleWeekDaysTip) False
|
||||
themeList = [Option (toMessage t) t (toPathPiece t) | t <- universeF]
|
||||
|
||||
schoolsForm :: Maybe (Set SchoolId) -> AForm Handler (Set SchoolId)
|
||||
schoolsForm template = formToAForm $ schoolsFormView =<< renderWForm FormStandard schoolsForm' mempty
|
||||
@ -404,11 +375,6 @@ validateSettings User{..} = do
|
||||
userDisplayName == userDisplayName' || -- unchanged or valid (invalid displayNames delivered by LDAP are preserved)
|
||||
validDisplayName userTitle userFirstName userSurname userDisplayName'
|
||||
|
||||
userScheduleWeekTimeFrom' <- use _stgScheduleWeekTimeFrom
|
||||
userScheduleWeekTimeTo' <- use _stgScheduleWeekTimeTo
|
||||
|
||||
guardValidation MsgScheduleWeekTimeToMustBeAfterTimeFrom
|
||||
$ userScheduleWeekTimeTo' > userScheduleWeekTimeFrom'
|
||||
userDisplayEmail' <- use _stgDisplayEmail
|
||||
guardValidation (MsgInvalidEmailAddressWith $ CI.original userDisplayEmail') $
|
||||
validEmail' userDisplayEmail' || -- valid
|
||||
@ -479,19 +445,12 @@ serveProfileR (uid, user@User{..}) = do
|
||||
let settingsTemplate = Just SettingsForm
|
||||
{ stgDisplayName = userDisplayName
|
||||
, stgDisplayEmail = if userDisplayEmail == "" then userEmail else userDisplayEmail
|
||||
, stgScheduleView = userScheduleView
|
||||
, stgScheduleWeekDays = userScheduleWeekDays
|
||||
, stgScheduleWeekTimeFrom = userScheduleWeekTimeFrom
|
||||
, stgScheduleWeekTimeTo = userScheduleWeekTimeTo
|
||||
, stgScheduleWeekTimeslotLength = userScheduleWeekTimeslotLength
|
||||
, stgScheduleOccurrenceDisplayDefault = userScheduleOccurrenceDisplayDefault
|
||||
, stgMaxFavourites = userMaxFavourites
|
||||
, stgMaxFavouriteTerms = userMaxFavouriteTerms
|
||||
, stgTheme = userTheme
|
||||
, stgDateTime = userDateTimeFormat
|
||||
, stgDate = userDateFormat
|
||||
, stgTime = userTimeFormat
|
||||
, stgWeekStart = userWeekStart
|
||||
, stgDownloadFiles = userDownloadFiles
|
||||
, stgSchools = userSchools
|
||||
, stgNotificationSettings = userNotificationSettings
|
||||
@ -521,19 +480,12 @@ serveProfileR (uid, user@User{..}) = do
|
||||
[ UserDisplayEmail =. stgDisplayEmail | not changeEmailByUser, changeEmailProper ] ++ -- DisplayEmail changes by Users must be confirmed, see 480 below
|
||||
[ UserPostLastUpdate =. Just now | userPostAddress /= stgPostAddress ] ++
|
||||
[ UserDisplayName =. stgDisplayName
|
||||
, UserScheduleView =. stgScheduleView
|
||||
, UserScheduleWeekDays =. stgScheduleWeekDays
|
||||
, UserScheduleWeekTimeFrom =. stgScheduleWeekTimeFrom
|
||||
, UserScheduleWeekTimeTo =. stgScheduleWeekTimeTo
|
||||
, UserScheduleWeekTimeslotLength =. stgScheduleWeekTimeslotLength
|
||||
, UserScheduleOccurrenceDisplayDefault =. stgScheduleOccurrenceDisplayDefault
|
||||
, UserMaxFavourites =. stgMaxFavourites
|
||||
, UserMaxFavouriteTerms =. stgMaxFavouriteTerms
|
||||
, UserTheme =. stgTheme
|
||||
, UserDateTimeFormat =. stgDateTime
|
||||
, UserDateFormat =. stgDate
|
||||
, UserTimeFormat =. stgTime
|
||||
, UserWeekStart =. stgWeekStart
|
||||
, UserDownloadFiles =. stgDownloadFiles
|
||||
, UserWarningDays =. stgWarningDays
|
||||
, UserNotificationSettings =. stgNotificationSettings
|
||||
|
||||
@ -1,105 +0,0 @@
|
||||
module Handler.Schedule
|
||||
( getScheduleR, postScheduleR
|
||||
) where
|
||||
|
||||
import Import
|
||||
|
||||
import Handler.Utils.Form
|
||||
import Handler.Utils.I18n
|
||||
|
||||
import Utils.Schedule.Types
|
||||
import Utils.Schedule.Week
|
||||
|
||||
|
||||
getScheduleR, postScheduleR :: Handler Html
|
||||
getScheduleR = postScheduleR
|
||||
postScheduleR = do
|
||||
now <- liftIO getCurrentTime
|
||||
user@(Entity _uid User{userScheduleView}) <- requireAuth
|
||||
|
||||
-- TODO: local instead of global get params?
|
||||
mOptions <- lookupGlobalGetParam GetScheduleOptions
|
||||
|
||||
let
|
||||
defaultScheduleOptions :: ScheduleOptions
|
||||
defaultScheduleOptions = ScheduleOptions
|
||||
{ scheduleView = userScheduleView
|
||||
, scheduleOffset = ScheduleOffsetNone
|
||||
, scheduleOptionsAction = ScheduleSetDefault
|
||||
}
|
||||
|
||||
currentScheduleOptions :: ScheduleOptions
|
||||
currentScheduleOptions = fromMaybe defaultScheduleOptions mOptions
|
||||
|
||||
scheduleOptionsForm :: Html -> MForm (HandlerFor UniWorX) (FormResult ScheduleOptions, Widget)
|
||||
scheduleOptionsForm csrf = do
|
||||
(viewRess, viewWidgets) <- fmap unzip . for (universeF :: [ScheduleView]) $ \sView ->
|
||||
mopt (buttonFieldNoParse ScheduleOptions
|
||||
{ scheduleView = sView
|
||||
, scheduleOffset = ScheduleOffsetNone
|
||||
, scheduleOptionsAction = ScheduleSetView
|
||||
})
|
||||
("" { fsName = Just $ toPathPiece GetScheduleOptions
|
||||
, fsAttrs = if sView == scheduleView currentScheduleOptions then [("disabled","")] else mempty
|
||||
}) Nothing
|
||||
viewRes <- if
|
||||
| Just errs <- fromNullable (filter (is _FormFailure) viewRess) -> do
|
||||
mapM_ formFailure2Alerts errs
|
||||
(return . FormFailure . mconcat . catMaybes . (fmap $ preview _FormFailure) . toNullable) errs
|
||||
| otherwise -> case catMaybes $ catMaybes (formResultToMaybe <$> viewRess) of
|
||||
[ScheduleOptions{scheduleView=sView}] -> (return . FormSuccess) $ ScheduleOptions
|
||||
{ scheduleView = sView
|
||||
, scheduleOffset = ScheduleOffsetNone
|
||||
, scheduleOptionsAction = ScheduleSetView
|
||||
}
|
||||
_ -> return $ FormSuccess $ currentScheduleOptions
|
||||
{ scheduleOffset = ScheduleOffsetNone
|
||||
, scheduleOptionsAction = ScheduleSetView
|
||||
}
|
||||
|
||||
let
|
||||
offsetBtns = case viewRes of
|
||||
FormSuccess ScheduleOptions{scheduleView=ScheduleViewWeek}
|
||||
-> weekOffsets now user (scheduleOffset currentScheduleOptions)
|
||||
<&> (\sNewOffset -> currentScheduleOptions
|
||||
{ scheduleOffset = case sNewOffset of
|
||||
ScheduleOffsetNone -> ScheduleOffsetNone
|
||||
_ -> scheduleOffset currentScheduleOptions `addOffset` sNewOffset
|
||||
, scheduleOptionsAction = ScheduleSetOffset sNewOffset
|
||||
})
|
||||
_ -> mempty
|
||||
|
||||
(offsetRess, offsetWidgets) <- fmap unzip . for offsetBtns $ \btn ->
|
||||
mopt (buttonFieldNoParse btn) ("" { fsName = Just $ toPathPiece GetScheduleOptions
|
||||
, fsAttrs = if ((==) `on `offsetInDays) (scheduleOffset btn) (scheduleOffset currentScheduleOptions)
|
||||
then [("disabled","")]
|
||||
else mempty
|
||||
}) Nothing
|
||||
offsetRes <- if
|
||||
| Just errs <- fromNullable (filter (is _FormFailure) offsetRess) -> do
|
||||
mapM_ formFailure2Alerts errs
|
||||
(return . FormFailure . mconcat . catMaybes . (fmap $ preview _FormFailure) . toNullable) errs
|
||||
| otherwise -> case catMaybes $ catMaybes (formResultToMaybe <$> offsetRess) of
|
||||
[opt] -> return $ FormSuccess opt
|
||||
_ -> return $ FormSuccess currentScheduleOptions
|
||||
|
||||
let
|
||||
scheduleResult = case (viewRes, offsetRes) of
|
||||
(_, opts@(FormSuccess _)) -> opts
|
||||
(opts@(FormSuccess _), _) -> opts
|
||||
_ -> FormSuccess currentScheduleOptions
|
||||
optionsWidget = $(widgetFile "schedule/options")
|
||||
|
||||
return (scheduleResult, optionsWidget)
|
||||
|
||||
((optionsRes, optionsWidget), optionsEnctype) <- runFormGet scheduleOptionsForm
|
||||
|
||||
schedule <- case optionsRes of
|
||||
FormSuccess ScheduleOptions{scheduleView=ScheduleViewWeek,..} -> return $ weekSchedule now user scheduleOffset
|
||||
other -> formFailure2Alerts other >> return mempty
|
||||
|
||||
let scheduleExplanation = $(i18nWidgetFile "schedule-explanation")
|
||||
|
||||
siteLayoutMsg MsgMenuSchedule $ do
|
||||
setTitleI MsgMenuSchedule
|
||||
$(widgetFile "schedule")
|
||||
@ -13,6 +13,5 @@ import Handler.Tutorial.Form as Handler.Tutorial
|
||||
import Handler.Tutorial.List as Handler.Tutorial
|
||||
import Handler.Tutorial.New as Handler.Tutorial
|
||||
import Handler.Tutorial.Register as Handler.Tutorial
|
||||
import Handler.Tutorial.Schedule as Handler.Tutorial
|
||||
import Handler.Tutorial.TutorInvite as Handler.Tutorial
|
||||
import Handler.Tutorial.Users as Handler.Tutorial
|
||||
|
||||
@ -1,40 +0,0 @@
|
||||
module Handler.Tutorial.Schedule
|
||||
( getTScheduleOptSetR, postTScheduleOptSetR
|
||||
, getTScheduleOptDelR, postTScheduleOptDelR
|
||||
) where
|
||||
|
||||
import Import
|
||||
|
||||
import Handler.Utils.Tutorial
|
||||
|
||||
|
||||
getTScheduleOptSetR, postTScheduleOptSetR :: TermId -> SchoolId -> CourseShorthand -> TutorialName -> Bool -> Handler Html
|
||||
getTScheduleOptSetR = postTScheduleOptSetR
|
||||
postTScheduleOptSetR tid ssh csh tutn opt = do
|
||||
uid <- requireAuthId
|
||||
|
||||
runDB $ do
|
||||
tutid <- fmap entityKey $ fetchTutorial tid ssh csh tutn
|
||||
void $ upsert TutorialScheduleOpt
|
||||
{ tutorialScheduleOptTutorial = tutid
|
||||
, tutorialScheduleOptUser = uid
|
||||
, tutorialScheduleOptOpt = opt
|
||||
}
|
||||
[ TutorialScheduleOptOpt =. opt
|
||||
]
|
||||
|
||||
addMessageI Success $ bool MsgScheduleOptOutSuccess MsgScheduleOptInSuccess opt
|
||||
redirect $ CourseR tid ssh csh CShowR
|
||||
|
||||
|
||||
getTScheduleOptDelR, postTScheduleOptDelR :: TermId -> SchoolId -> CourseShorthand -> TutorialName -> Handler Html
|
||||
getTScheduleOptDelR = postTScheduleOptDelR
|
||||
postTScheduleOptDelR tid ssh csh tutn = do
|
||||
uid <- requireAuthId
|
||||
|
||||
runDB $ do
|
||||
tutid <- fmap entityKey $ fetchTutorial tid ssh csh tutn
|
||||
deleteBy $ UniqueTutorialScheduleOpt tutid uid
|
||||
|
||||
addMessageI Success MsgScheduleOptDeleteSuccess
|
||||
redirect $ CourseR tid ssh csh CShowR
|
||||
@ -329,6 +329,8 @@ updateAvsUserByADC newAvsDataContact@(AvsDataContact apid newAvsPersonInfo newAv
|
||||
let usrId = userAvsUser usravs
|
||||
usr <- MaybeT $ get usrId
|
||||
lift $ do -- maybeT no longer needed from here onwards
|
||||
uuid :: CryptoUUIDUser <- encrypt usrId
|
||||
$logInfoS "AVS" [st|updateAvsUserByADC: #{tshow uuid}|]
|
||||
let oldAvsPersonInfo = userAvsLastPersonInfo usravs -- Nothing is ok here
|
||||
oldAvsFirmInfo = userAvsLastFirmInfo usravs -- Nothing is ok here
|
||||
oldAvsCardNo = userAvsLastCardNo usravs & fmap Just
|
||||
@ -380,72 +382,73 @@ updateAvsUserByADC newAvsDataContact@(AvsDataContact apid newAvsPersonInfo newAv
|
||||
, UserAvsLastCardNo =. newAvsCardNo
|
||||
]
|
||||
|
||||
-- update company association & supervision
|
||||
newCompanyEnt@Entity{entityKey=newCompanyId} <- upsertAvsCompany newAvsFirmInfo oldAvsFirmInfo
|
||||
oldCompanyEnt <- getAvsCompany `traverseJoin` oldAvsFirmInfo
|
||||
primaryCompanyId <- userCompanyCompany <<$>> getUserPrimaryCompany usrId
|
||||
let oldCompanyId = entityKey <$> oldCompanyEnt
|
||||
-- oldCompanyMb = entityVal <$> oldCompanyEnt
|
||||
-- pst_up = if
|
||||
-- -- | isNothing oldCompanyMb || oldCompanyId == primaryCompanyId -- refactor could replace next 4 lines
|
||||
-- -- -> mkUpdate' usr newCompany oldCompanyMb $ CheckUpdate UserPrefersPostal _companyPrefersPostal -- always update if company association is fresh (case should not occur in practice though)
|
||||
-- | isNothing oldCompanyMb
|
||||
-- -> mkUpdateDirect usr newCompany $ CheckUpdate UserPrefersPostal _companyPrefersPostal -- always update if company association is fresh (case should not occur in practice though)
|
||||
-- | oldCompanyId == primaryCompanyId -- && isJust oldCompanyId -- is ensured by previous line
|
||||
-- -> mkUpdate usr newCompany oldCompanyMb $ CheckUpdate UserPrefersPostal _companyPrefersPostal -- possibly change postal preference
|
||||
-- | otherwise
|
||||
-- -> Nothing
|
||||
superReasonComDef = tshow SupervisorReasonCompanyDefault
|
||||
newUserComp = UserCompany usrId newCompanyId False False 1 True Nothing -- default value for new company insertion, if no update can be done
|
||||
usr_up2 <- guardMonoidM (oldAvsFirmInfo /= Just newAvsFirmInfo) $ do
|
||||
-- update company association & supervision
|
||||
newCompanyEnt@Entity{entityKey=newCompanyId} <- upsertAvsCompany newAvsFirmInfo oldAvsFirmInfo
|
||||
upsertCompanySuperior newCompanyEnt newAvsFirmInfo oldAvsFirmInfo usrId -- ensure firmInfo superior is supervisor for this user
|
||||
oldCompanyEnt <- getAvsCompany `traverseJoin` oldAvsFirmInfo
|
||||
primaryCompanyId <- userCompanyCompany <<$>> getUserPrimaryCompany usrId
|
||||
let oldCompanyId = entityKey <$> oldCompanyEnt
|
||||
-- oldCompanyMb = entityVal <$> oldCompanyEnt
|
||||
-- pst_up = if
|
||||
-- -- | isNothing oldCompanyMb || oldCompanyId == primaryCompanyId -- refactor could replace next 4 lines
|
||||
-- -- -> mkUpdate' usr newCompany oldCompanyMb $ CheckUpdate UserPrefersPostal _companyPrefersPostal -- always update if company association is fresh (case should not occur in practice though)
|
||||
-- | isNothing oldCompanyMb
|
||||
-- -> mkUpdateDirect usr newCompany $ CheckUpdate UserPrefersPostal _companyPrefersPostal -- always update if company association is fresh (case should not occur in practice though)
|
||||
-- | oldCompanyId == primaryCompanyId -- && isJust oldCompanyId -- is ensured by previous line
|
||||
-- -> mkUpdate usr newCompany oldCompanyMb $ CheckUpdate UserPrefersPostal _companyPrefersPostal -- possibly change postal preference
|
||||
-- | otherwise
|
||||
-- -> Nothing
|
||||
superReasonComDef = tshow SupervisorReasonCompanyDefault
|
||||
newUserComp = UserCompany usrId newCompanyId False False 1 True Nothing -- default value for new company insertion, if no update can be done
|
||||
|
||||
usr_up2 <- case oldAvsFirmInfo of
|
||||
_ | Just newCompanyId == oldCompanyId -- company unchanged entirely
|
||||
-> return mempty -- => do nothing
|
||||
(Just oafi) | isJust (view _avsFirmPostAddressSimple oafi)
|
||||
&& ((==) `on` view _avsFirmPostAddressSimple) oafi newAvsFirmInfo -- non-empty company address unchanged OR
|
||||
|| isJust (view _avsFirmPrimaryEmail oafi)
|
||||
&& ((==) `on` view _avsFirmPrimaryEmail) oafi newAvsFirmInfo -- non-empty company primary email unchanged
|
||||
-> do -- => just update user company association, keeping supervision privileges
|
||||
case oldCompanyId of
|
||||
Nothing -> void $ insertUnique newUserComp -- it's ok if this already exists
|
||||
Just ocid -> do
|
||||
void $ upsertBySafe (UniqueUserCompany usrId ocid) newUserComp (_userCompanyCompany .~ newCompanyId) -- keep default supervisor settings
|
||||
void $ updateWhere [ UserSupervisorSupervisor ==. usrId -- update company-related supervisions
|
||||
, UserSupervisorCompany ==. Just ocid -- to new company, regardless of
|
||||
, UserSupervisorReason ==. Just superReasonComDef] -- user
|
||||
[ UserSupervisorCompany =. Just newCompanyId]
|
||||
return mempty
|
||||
_ | Just newCompanyId == primaryCompanyId -- old primaryCompany is now also AVS-company
|
||||
-> do
|
||||
whenIsJust oldCompanyId $ \oldCid -> do
|
||||
deleteBy $ UniqueUserCompany usrId oldCid
|
||||
deleteWhere $ (UserSupervisorUser ==. usrId):(UserSupervisorCompany ==. oldCompanyId):(UserSupervisorReason ~=. superReasonComDef)
|
||||
return mempty
|
||||
_ -- company changed completely
|
||||
-> do
|
||||
(pst_up, problems) <- switchAvsUserCompany False False usrId newCompanyId
|
||||
mapM_ reportAdminProblem problems
|
||||
-- Following line does not type, hence additional parameter needed
|
||||
-- return [ u | u@Update{updateField=f} <- pst_up, f /= UserPostAddress ] -- already computed in frm_up above, duplicate update must be prevented (version above accounts for legacy updates)
|
||||
return pst_up
|
||||
-- SPECIALISED CODE, PROBABLY DEPRECATED
|
||||
-- switch user company, keeping old priority
|
||||
-- (getBy . UniqueUserCompany usrId) `traverseJoin` oldCompanyId >>= \case
|
||||
-- Nothing ->
|
||||
-- void $ insertUnique newUserComp
|
||||
-- Just Entity{entityKey=ucidOld, entityVal=UserCompany{userCompanyCompany, userCompanySupervisor, userCompanySupervisorReroute, userCompanyPriority}} -> do
|
||||
-- when userCompanySupervisor $ reportAdminProblem $ AdminProblemSupervisorNewCompany usrId userCompanyCompany newCompanyId userCompanySupervisorReroute
|
||||
-- delete ucidOld
|
||||
-- void $ insertUnique newUserComp{userCompanyPriority} -- keep priority, if insert succeeds
|
||||
-- -- adjust supervison
|
||||
-- let oldCompDefSuperFltr = mconcat [UserSupervisorCompany ~~. oldCompanyId, UserSupervisorReason ~=. superReasonComDef]
|
||||
-- deleteWhere $ (UserSupervisorSupervisor ==. usrId) : oldCompDefSuperFltr
|
||||
-- oldAPs <- deleteWhereCount $ (UserSupervisorUser ==. usrId) : oldCompDefSuperFltr
|
||||
-- addDefaultSupervisors' newCompanyId $ singleton usrId
|
||||
-- newAPs <- count $ (UserSupervisorUser ==. usrId) : (UserSupervisorCompany ==. Just newCompanyId) : (UserSupervisorReason ~=. superReasonComDef)
|
||||
-- when (oldAPs > 0 && newAPs <= 0) $ reportAdminProblem $ AdminProblemNewlyUnsupervised usrId oldCompanyId newCompanyId
|
||||
-- return pst_up
|
||||
upsertCompanySuperior newCompanyEnt newAvsFirmInfo oldAvsFirmInfo [usrId] -- ensure firmInfo superior is supervisor for this user
|
||||
case oldAvsFirmInfo of
|
||||
_ | Just newCompanyId == oldCompanyId -- company unchanged entirely
|
||||
-> return mempty -- => do nothing
|
||||
(Just oafi) | isJust (view _avsFirmPostAddressSimple oafi)
|
||||
&& ((==) `on` view _avsFirmPostAddressSimple) oafi newAvsFirmInfo -- non-empty company address unchanged OR
|
||||
|| isJust (view _avsFirmPrimaryEmail oafi)
|
||||
&& ((==) `on` view _avsFirmPrimaryEmail) oafi newAvsFirmInfo -- non-empty company primary email unchanged
|
||||
-> do -- => just update user company association, keeping supervision privileges
|
||||
case oldCompanyId of
|
||||
Nothing -> void $ insertUnique newUserComp -- it's ok if this already exists
|
||||
Just ocid -> do
|
||||
void $ upsertBySafe (UniqueUserCompany usrId ocid) newUserComp (_userCompanyCompany .~ newCompanyId) -- keep default supervisor settings
|
||||
void $ updateWhere [ UserSupervisorSupervisor ==. usrId -- update company-related supervisions
|
||||
, UserSupervisorCompany ==. Just ocid -- to new company, regardless of
|
||||
, UserSupervisorReason ==. Just superReasonComDef] -- user
|
||||
[ UserSupervisorCompany =. Just newCompanyId]
|
||||
return mempty
|
||||
_ | Just newCompanyId == primaryCompanyId -- old primaryCompany is now also AVS-company
|
||||
-> do
|
||||
whenIsJust oldCompanyId $ \oldCid -> do
|
||||
deleteBy $ UniqueUserCompany usrId oldCid
|
||||
deleteWhere $ (UserSupervisorUser ==. usrId):(UserSupervisorCompany ==. oldCompanyId):(UserSupervisorReason ~=. superReasonComDef)
|
||||
return mempty
|
||||
_ -- company changed completely
|
||||
-> do
|
||||
(pst_up, problems) <- switchAvsUserCompany False False usrId newCompanyId
|
||||
mapM_ reportAdminProblem problems
|
||||
-- Following line does not type, hence additional parameter needed
|
||||
-- return [ u | u@Update{updateField=f} <- pst_up, f /= UserPostAddress ] -- already computed in frm_up above, duplicate update must be prevented (version above accounts for legacy updates)
|
||||
return pst_up
|
||||
-- SPECIALISED CODE, PROBABLY DEPRECATED
|
||||
-- switch user company, keeping old priority
|
||||
-- (getBy . UniqueUserCompany usrId) `traverseJoin` oldCompanyId >>= \case
|
||||
-- Nothing ->
|
||||
-- void $ insertUnique newUserComp
|
||||
-- Just Entity{entityKey=ucidOld, entityVal=UserCompany{userCompanyCompany, userCompanySupervisor, userCompanySupervisorReroute, userCompanyPriority}} -> do
|
||||
-- when userCompanySupervisor $ reportAdminProblem $ AdminProblemSupervisorNewCompany usrId userCompanyCompany newCompanyId userCompanySupervisorReroute
|
||||
-- delete ucidOld
|
||||
-- void $ insertUnique newUserComp{userCompanyPriority} -- keep priority, if insert succeeds
|
||||
-- -- adjust supervison
|
||||
-- let oldCompDefSuperFltr = mconcat [UserSupervisorCompany ~~. oldCompanyId, UserSupervisorReason ~=. superReasonComDef]
|
||||
-- deleteWhere $ (UserSupervisorSupervisor ==. usrId) : oldCompDefSuperFltr
|
||||
-- oldAPs <- deleteWhereCount $ (UserSupervisorUser ==. usrId) : oldCompDefSuperFltr
|
||||
-- addDefaultSupervisors' newCompanyId $ singleton usrId
|
||||
-- newAPs <- count $ (UserSupervisorUser ==. usrId) : (UserSupervisorCompany ==. Just newCompanyId) : (UserSupervisorReason ~=. superReasonComDef)
|
||||
-- when (oldAPs > 0 && newAPs <= 0) $ reportAdminProblem $ AdminProblemNewlyUnsupervised usrId oldCompanyId newCompanyId
|
||||
-- return pst_up
|
||||
update usrId usr_up2 -- update user by company switch first (due to possible conflicts with usr_up2)
|
||||
update usrId usr_up1 -- update user eventually
|
||||
update uaId avs_ups -- update stored avsinfo for future updates
|
||||
@ -585,16 +588,18 @@ getAvsCompany afi =
|
||||
|
||||
-- | insert a company from AVS firm info or update an existing one based on previous values
|
||||
upsertAvsCompany :: AvsFirmInfo -> Maybe AvsFirmInfo -> DB (Entity Company)
|
||||
-- upsertAvsCompany newAvsFirmInfo (Just oldAvsFirmInfo)
|
||||
-- | newAvsFirmInfo == oldAvsFirmInfo = maybeM (upsertAvsCompany newAvsFirmInfo Nothing) pure $ getAvsCompany newAvsFirmInfo -- firmInfo unchanged, shortcircuit; SHORTCIRCUIT no longer needed, checked at call-site due to result not being wrapped in Maybe
|
||||
upsertAvsCompany newAvsFirmInfo mbOldAvsFirmInfo = do
|
||||
mbFirmEnt <- getAvsCompany newAvsFirmInfo -- primarily by AvsId, then Shorthand, then name
|
||||
$logInfoS "AVS" [st|upsertAvsCompany: old #{tshow mbFirmEnt} new #{tshow newAvsFirmInfo}|]
|
||||
case (mbFirmEnt, mbOldAvsFirmInfo) of
|
||||
(Nothing, _) -> do -- insert new company, neither AvsId nor Shorthand exist in DB
|
||||
$logInfoS "AVS" [st|upsertAvsCompany: old #{tshow mbOldAvsFirmInfo} new #{tshow newAvsFirmInfo} ent-new #{tshow mbFirmEnt}|]
|
||||
case mbFirmEnt of
|
||||
Nothing -> do -- insert new company, neither AvsId nor Shorthand exist in DB
|
||||
afn <- if 0 < newAvsFirmInfo ^. _avsFirmFirmNo
|
||||
then return $ newAvsFirmInfo ^. _avsFirmFirmNo
|
||||
else maybe (-1) (pred . companyAvsId . entityVal) <$> selectMaybe [CompanyAvsId <. 0] [Asc CompanyAvsId]
|
||||
let upd = flip updateRecord newAvsFirmInfo
|
||||
dmy = Company -- mostly dummy, values are actually prodcued through firmInfo2company below for consistency
|
||||
dmy = Company -- mostly dummy, values are actually produced through firmInfo2company below for consistency
|
||||
{ companyName = newAvsFirmInfo ^. _avsFirmFirm . from _CI
|
||||
, companyShorthand = newAvsFirmInfo ^. _avsFirmAbbreviation . from _CI
|
||||
, companyAvsId = afn
|
||||
@ -606,11 +611,12 @@ upsertAvsCompany newAvsFirmInfo mbOldAvsFirmInfo = do
|
||||
$logInfoS "AVS" $ "Insert new company: " <> tshow cmp
|
||||
newCmp <- insertEntity cmp
|
||||
reportAdminProblem $ AdminProblemNewCompany $ entityKey newCmp
|
||||
$logInfoS "AVS" "Insert new company completed."
|
||||
return newCmp
|
||||
|
||||
(Just Entity{entityKey=firmid, entityVal=firm}, oldAvsFirmInfo) -> do -- possibly update existing company, if isJust oldAvsFirmInfo and changed occurred
|
||||
let cmp_ups = mapMaybe (mkUpdate' firm newAvsFirmInfo oldAvsFirmInfo) firmInfo2company
|
||||
(Just Entity{entityKey=firmid, entityVal=firm}) -> do -- possibly update existing company, if isJust oldAvsFirmInfo and identical AvsFirmNo and changes occurred
|
||||
let oldHasSameFirmNo = Just (newAvsFirmInfo ^. _avsFirmFirmNo) == (mbOldAvsFirmInfo ^? _Just . _avsFirmFirmNo)
|
||||
oldAvsFirmInfo = guardOnM oldHasSameFirmNo mbOldAvsFirmInfo
|
||||
cmp_ups = mapMaybe (mkUpdate' firm newAvsFirmInfo oldAvsFirmInfo) firmInfo2company
|
||||
key_ups = mkUpdate' firm newAvsFirmInfo oldAvsFirmInfo firmInfo2key
|
||||
uniq_ups <- mkUpdateCheckUnique' firm newAvsFirmInfo oldAvsFirmInfo firmInfo2companyNo
|
||||
$logInfoS "AVS" [st|Update company #{companyShorthand firm}: #{tshow (length cmp_ups)}, #{tshow (length key_ups)}, #{tshow (length uniq_ups)} for #{tshow oldAvsFirmInfo}|]
|
||||
@ -629,7 +635,7 @@ upsertAvsCompany newAvsFirmInfo mbOldAvsFirmInfo = do
|
||||
| otherwise -> $logInfoS "AVS" $ "Update company shorthand failed for " <> ciOriginal cmp_key <> " and " <> ciOriginal alt_key
|
||||
maybeM (return res_cmp) return $ getBy uniq_cmp
|
||||
_otherwise -> return res_cmp
|
||||
$logInfoS "AVS" "Update company completed."
|
||||
$logInfoS "AVS" [st|Update company #{companyShorthand firm} completed.|]
|
||||
return res_cmp2
|
||||
where
|
||||
firmInfo2key =
|
||||
@ -645,8 +651,8 @@ upsertAvsCompany newAvsFirmInfo mbOldAvsFirmInfo = do
|
||||
|
||||
|
||||
-- | adjust superiors, assumes that CompanyUser exists for all usrs for given company; does not work otherwise
|
||||
upsertCompanySuperior :: Entity Company -> AvsFirmInfo -> Maybe AvsFirmInfo -> [UserId] -> DB () -- may return superior (Maybe UserId), but currently not needed
|
||||
upsertCompanySuperior Entity{entityKey=cid, entityVal=Company{}} newAfi oldAfi usrs =
|
||||
upsertCompanySuperior :: Entity Company -> AvsFirmInfo -> Maybe AvsFirmInfo -> UserId -> DB () -- may return superior (Maybe UserId), but currently not needed
|
||||
upsertCompanySuperior Entity{entityKey=cid, entityVal=Company{}} newAfi oldAfi usrId =
|
||||
let reasonSuperior = Just $ tshow SupervisorReasonAvsSuperior
|
||||
getInsertUid eml = altM (guessUserByEmail $ stripCI eml) (catchAll2log' $ Just . entityKey <$> ldapLookupAndUpsert eml)
|
||||
newAvsNo = newAfi ^. _avsFirmFirmNo
|
||||
@ -655,22 +661,26 @@ upsertCompanySuperior Entity{entityKey=cid, entityVal=Company{}} newAfi oldAfi u
|
||||
mbOldEmail = oldAfi ^? _Just . _avsFirmEMailSuperior . _Just
|
||||
getSupId = getInsertUid `traverseJoin` mbSupEmail
|
||||
getOldId = (guessUserByEmail . stripCI) `traverseJoin` mbOldEmail
|
||||
unchangedCompany = oldAvsNo == Just newAvsNo
|
||||
changedSuperior = mbSupEmail /= mbOldEmail -- beware, both could be Nothing
|
||||
getSupervision :: Maybe UserId -> DB (Maybe (Entity UserSupervisor))
|
||||
getSupervision = traverseJoin (getBy . flip UniqueUserSupervisor usrId)
|
||||
unchangedCompany = oldAvsNo == Just newAvsNo
|
||||
changedSuperior = mbSupEmail /= mbOldEmail -- beware we only have AvsFirmInfo for one user; also both could be Nothing
|
||||
-- 1. not unchangedCompany: do not delete, but ensure that superior supervision is set, since it could be a just a single user company change
|
||||
-- 2. unchangedCompany && not changedSuperior: superior must already been set, short-circuit
|
||||
-- 3. unchangedCompany && changedSuperior: update superior for all users
|
||||
in unless (unchangedCompany && not changedSuperior) $ do -- do nothing if (unchangedCompany && not changedSuperior).
|
||||
mbSupId <- getSupId
|
||||
mbSupId <- getSupId
|
||||
mbUsrSup <- getSupervision mbSupId
|
||||
-- delete old superiors, if any
|
||||
when (unchangedCompany && changedSuperior) $
|
||||
deleteWhere $ mcons ((UserSupervisorSupervisor !=.) <$> mbSupId)
|
||||
[ UserSupervisorCompany ==. Just cid, UserSupervisorReason ==. reasonSuperior ]
|
||||
unless unchangedCompany $
|
||||
deleteWhere [ UserSupervisorReason ==. reasonSuperior, UserSupervisorUser <-. usrs ]
|
||||
deleteWhere [ UserSupervisorReason ==. reasonSuperior, UserSupervisorUser ==. usrId ]
|
||||
-- ensure superior supervision
|
||||
case mbSupId of
|
||||
Just supId -> do
|
||||
case (mbSupId, mbUsrSup) of
|
||||
(_ , Just _) -> return () -- supId is already supervisor for uid for any reason
|
||||
(Just supId, Nothing) -> do
|
||||
-- ensure association between company and superior at equal-to-top priority
|
||||
prio <- getCompanyUserMaxPrio supId
|
||||
void $ insertUnique (UserCompany supId cid False False prio True reasonSuperior) -- superior is not a supervisor, do not change existing user company associations
|
||||
@ -702,7 +712,7 @@ upsertCompanySuperior Entity{entityKey=cid, entityVal=Company{}} newAfi oldAfi u
|
||||
when (unchangedCompany && changedSuperior) $ do
|
||||
oldSupId <- getOldId
|
||||
reportAdminProblem $ AdminProblemCompanySuperiorChange supId cid oldSupId
|
||||
Nothing ->
|
||||
(Nothing, Nothing) ->
|
||||
when (unchangedCompany && changedSuperior) $ do
|
||||
oldSupId <- getOldId
|
||||
reportAdminProblem $ AdminProblemCompanySuperiorNotFound mbSupEmail cid oldSupId
|
||||
|
||||
@ -183,16 +183,6 @@ instance Button UniWorX ButtonSubmitDelete where
|
||||
nullaryPathPiece ''ButtonSubmitDelete $ camelToPathPiece' 1 . dropSuffix "'"
|
||||
|
||||
|
||||
-- TODO: move
|
||||
data ButtonScheduleOpt = BtnScheduleOptIn | BtnScheduleOptOut | BtnScheduleOptDel
|
||||
deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
|
||||
instance Universe ButtonScheduleOpt
|
||||
instance Finite ButtonScheduleOpt
|
||||
nullaryPathPiece ''ButtonScheduleOpt $ camelToPathPiece' 1
|
||||
embedRenderMessage ''UniWorX ''ButtonScheduleOpt id
|
||||
instance Button UniWorX ButtonScheduleOpt where
|
||||
btnClasses = const [BCIsButton, BCPrimary]
|
||||
|
||||
-- | Looks like a button, but is just a link (e.g. for create course, etc.)
|
||||
linkButton :: Widget -- ^ Widget to display if unauthorized
|
||||
-> Widget -- ^ Button label
|
||||
@ -1399,7 +1389,7 @@ dayTimeField fs mutc = do
|
||||
|
||||
fieldTimeFormat :: String
|
||||
-- fieldTimeFormat = "%e.%m.%y %k:%M"
|
||||
fieldTimeFormat = "%Y-%m-%dT%H:%M:%S%Q"
|
||||
fieldTimeFormat = "%Y-%m-%dT%H:%M:%S"
|
||||
|
||||
localTimeField :: (MonadHandler m, HandlerSite m ~ UniWorX) => Field m LocalTime
|
||||
localTimeField = Field
|
||||
|
||||
@ -48,7 +48,7 @@ occurrencesAForm (toPathPiece -> miIdent') mPrev = wFormToAForm $ do
|
||||
(\p -> Just . SomeRoute $ cRoute :#: p)
|
||||
miLayout'
|
||||
(miIdent' <> "__scheduled" :: Text)
|
||||
(fslI MsgScheduleRegular & setTooltip MsgScheduleRegularTip)
|
||||
(fslI MsgScheduleRegular)
|
||||
False
|
||||
(Set.toList . occurrencesScheduled <$> mPrev)
|
||||
where
|
||||
|
||||
@ -122,11 +122,11 @@ dispatchJobSynchroniseAvsQueue = JobHandlerException $ do
|
||||
-- E.truncateTable $ AvsSync (error "truncateTable: AvsSyncUser not needed") now Nothing
|
||||
-- return jobs
|
||||
let (unlinked, linked) = foldl' discernJob mempty jobs
|
||||
$logInfoS "SynchronisAvs" [st|AVS synch performing for #{length linked} AVS linked users and #{length unlinked} unlinked users|]
|
||||
$logInfoS "SynchronisAvs" [st|AVS synch start for #{length linked} AVS linked users and #{length unlinked} unlinked users|]
|
||||
void $ updateAvsUserByIds linked
|
||||
void $ linktoAvsUserByUIDs unlinked
|
||||
runDB $ deleteWhere [AvsSyncUser <-. (E.unValue . fst3 <$> jobs)]
|
||||
$logInfoS "SynchronisAvs" [st|AVS synch performed for #{length linked} AVS linked users and #{length unlinked} unlinked users|]
|
||||
$logInfoS "SynchronisAvs" [st|AVS synch end for #{length linked} AVS linked users and #{length unlinked} unlinked users|]
|
||||
-- we do not reschedule failed synchs here in order to avoid a loop
|
||||
where
|
||||
discernJob accs ( _ , E.Value (Just api), E.Value True ) = accs & over _2 (Set.insert api)
|
||||
|
||||
@ -25,8 +25,6 @@ import Settings.Cluster (ClusterSettingsKey)
|
||||
|
||||
import Text.Blaze (ToMarkup(..))
|
||||
|
||||
import Utils.Schedule.Types.ScheduleView
|
||||
|
||||
import Database.Persist.Sql (BackendKey(..))
|
||||
|
||||
import qualified Database.Esqueleto.Legacy as E
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
-- SPDX-FileCopyrightText: 2022-2024 Sarah Vaupel <sarah.vaupel@uniworx.de>, Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <sarah.vaupel@ifi.lmu.de>,Sarah Vaupel <vaupel.sarah@campus.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>
|
||||
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <sarah.vaupel@ifi.lmu.de>,Sarah Vaupel <vaupel.sarah@campus.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>
|
||||
--
|
||||
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@ -47,18 +47,14 @@ type Companies = [CI Text]
|
||||
|
||||
type CourseName = CI Text
|
||||
type CourseShorthand = CI Text
|
||||
type CourseEventType = CI Text
|
||||
type CourseEventRoom = Text
|
||||
type MaterialName = CI Text
|
||||
type TutorialName = CI Text
|
||||
type TutorialType = CI Text
|
||||
type SheetName = CI Text
|
||||
type SubmissionGroupName = CI Text
|
||||
|
||||
type ExamName = CI Text
|
||||
type ExamPartName = CI Text
|
||||
type ExamOccurrenceName = CI Text
|
||||
type ExamOccurrenceRoom = Text
|
||||
|
||||
type PWHashAlgorithm = ByteString -> PWStore.Salt -> Int -> ByteString
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
-- SPDX-FileCopyrightText: 2022-2024 Sarah Vaupel <sarah.vaupel@uniworx.de>, Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <vaupel.sarah@campus.lmu.de>,Steffen Jost <jost@cip.ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>
|
||||
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <vaupel.sarah@campus.lmu.de>,Steffen Jost <jost@cip.ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>
|
||||
--
|
||||
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@ -34,8 +34,6 @@ import Web.HttpApiData
|
||||
|
||||
import Data.Aeson.Types as Aeson
|
||||
|
||||
import Model.Types.TH.PathPiece
|
||||
|
||||
|
||||
----
|
||||
-- Terms and anything loosely related to time
|
||||
@ -229,18 +227,6 @@ derivePersistFieldJSON ''Occurrences
|
||||
|
||||
|
||||
nullaryPathPiece ''DayOfWeek camelToPathPiece
|
||||
derivePersistFieldPathPiece ''DayOfWeek
|
||||
|
||||
|
||||
newtype ScheduleWeekDays = ScheduleWeekDays (Set DayOfWeek)
|
||||
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
||||
deriving newtype (Semigroup, Monoid)
|
||||
|
||||
instance NFData ScheduleWeekDays
|
||||
|
||||
deriveJSON defaultOptions ''ScheduleWeekDays
|
||||
derivePersistFieldJSON ''ScheduleWeekDays
|
||||
makeWrapped ''ScheduleWeekDays
|
||||
|
||||
|
||||
-- test :: IO [OccurrenceException]
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
-- SPDX-FileCopyrightText: 2022-2024 Sarah Vaupel <sarah.vaupel@uniworx.de>, Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <sarah.vaupel@ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>
|
||||
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <sarah.vaupel@ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>
|
||||
--
|
||||
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@ -79,8 +79,6 @@ import qualified Network.Minio as Minio
|
||||
|
||||
import Data.Conduit.Algorithms.FastCDC
|
||||
|
||||
import Utils.Schedule.Types.ScheduleView
|
||||
|
||||
import Utils.Lens.TH
|
||||
|
||||
import qualified Data.Set as Set
|
||||
@ -284,15 +282,9 @@ data UserDefaultConf = UserDefaultConf
|
||||
{ userDefaultTheme :: Theme
|
||||
, userDefaultMaxFavourites, userDefaultMaxFavouriteTerms :: Int
|
||||
, userDefaultDateTimeFormat, userDefaultDateFormat, userDefaultTimeFormat :: DateTimeFormat
|
||||
, userDefaultWeekStart :: DayOfWeek
|
||||
, userDefaultDownloadFiles :: Bool
|
||||
, userDefaultWarningDays :: NominalDiffTime
|
||||
, userDefaultShowSex :: Bool
|
||||
, userDefaultScheduleView :: ScheduleView
|
||||
, userDefaultScheduleWeekDays :: ScheduleWeekDays
|
||||
, userDefaultScheduleWeekTimeFrom, userDefaultScheduleWeekTimeTo :: NominalDiffTime
|
||||
, userDefaultScheduleWeekTimeslotLength :: NominalDiffTime
|
||||
, userDefaultScheduleOccurrenceDisplayDefault :: Bool
|
||||
, userDefaultExamOfficeGetSynced :: Bool
|
||||
, userDefaultExamOfficeGetLabels :: Bool
|
||||
, userDefaultPrefersPostal :: Bool
|
||||
|
||||
15
src/Utils.hs
15
src/Utils.hs
@ -831,21 +831,6 @@ listBracket b@(s,e) (h:t)
|
||||
----------
|
||||
-- all functions that used to be here are now in Utils.Set
|
||||
|
||||
funcFromSet :: Ord k => Set k -> (k -> Bool)
|
||||
funcFromSet = flip Set.member
|
||||
|
||||
_IndicatorFunction :: (Finite k, Ord k) => Iso' (Set k) (k -> Bool)
|
||||
_IndicatorFunction = iso funcFromSet setFromFunc
|
||||
|
||||
setFromMap :: Map k Bool -> Set k
|
||||
setFromMap = Map.keysSet . Map.filter id
|
||||
|
||||
mapFromSet :: Set k -> Map k Bool
|
||||
mapFromSet = Map.fromSet $ const True
|
||||
|
||||
_IndicatorMap :: Iso' (Set k) (Map k Bool)
|
||||
_IndicatorMap = iso mapFromSet setFromMap
|
||||
|
||||
----------
|
||||
-- Maps --
|
||||
----------
|
||||
|
||||
@ -16,7 +16,6 @@ module Utils.DateTime
|
||||
, mkDateTimeFormatter
|
||||
, nominalHour, nominalMinute
|
||||
, minNominalYear, avgNominalYear
|
||||
, nominalTimeToTimeOfDay, timeOfDayToNominalTime
|
||||
, diffMinute, diffHour, diffDay
|
||||
, module Zones
|
||||
, day
|
||||
@ -34,7 +33,6 @@ import Data.Time.Zones (localTimeToUTCTZ, timeZoneForUTCTime)
|
||||
import Data.Time.Format (FormatTime)
|
||||
import Data.Time.Format.Instances ()
|
||||
import Data.Time.Clock.System (systemEpochDay)
|
||||
import Data.Time.LocalTime (timeToTimeOfDay, timeOfDayToTime)
|
||||
import qualified Data.Time.Format.ISO8601 as Time
|
||||
import qualified Data.Time.Format as Time
|
||||
-- import Data.Time.Calendar (CalendarDiffDays, calendarMonth, scaleCalendarDiffDays)
|
||||
@ -164,11 +162,6 @@ minNominalYear, avgNominalYear :: NominalDiffTime
|
||||
minNominalYear = 365 * nominalDay
|
||||
avgNominalYear = fromRational $ 365.2425 * toRational nominalDay
|
||||
|
||||
nominalTimeToTimeOfDay :: NominalDiffTime -> TimeOfDay
|
||||
nominalTimeToTimeOfDay = timeToTimeOfDay . realToFrac
|
||||
timeOfDayToNominalTime :: TimeOfDay -> NominalDiffTime
|
||||
timeOfDayToNominalTime = realToFrac . timeOfDayToTime
|
||||
|
||||
--------------
|
||||
-- DiffTime --
|
||||
--------------
|
||||
|
||||
@ -425,27 +425,6 @@ buttonField btn = Field{..}
|
||||
| otherwise = return . Left $ SomeMessage MsgWrongButtonValue
|
||||
fieldParse _ _ = return . Left $ SomeMessage MsgMultipleButtonValues
|
||||
|
||||
-- | Similar to buttonField, but with dummy fieldParse
|
||||
buttonFieldNoParse :: forall a m.
|
||||
( Button (HandlerSite m) a
|
||||
, MonadHandler m
|
||||
) => a -> Field m a
|
||||
buttonFieldNoParse btn = Field{..}
|
||||
where
|
||||
fieldEnctype = UrlEncoded
|
||||
|
||||
fieldView :: FieldViewFunc m a
|
||||
fieldView fid name attrs _val _ = let
|
||||
validate = btnValidate (Proxy @(HandlerSite m)) btn
|
||||
classes :: [ButtonClass (HandlerSite m)]
|
||||
classes = btnClasses btn
|
||||
in [whamlet|
|
||||
$newline never
|
||||
<button class=#{unwords $ map toPathPiece classes} type=submit name=#{name} value=#{toPathPiece btn} *{attrs} ##{fid} :not validate:formnovalidate>^{btnLabel btn}
|
||||
|]
|
||||
|
||||
fieldParse _ _ = return . Right $ Just btn
|
||||
|
||||
combinedButtonField :: forall a m.
|
||||
( Button (HandlerSite m) a
|
||||
, MonadHandler m
|
||||
@ -832,28 +811,6 @@ daysField = convertField fromDays toDays fractionalField
|
||||
toDays = (/ nominalDay)
|
||||
fromDays = (* nominalDay)
|
||||
|
||||
timeOfDayField :: (Monad m, RenderMessage (HandlerSite m) FormMessage) => Field m NominalDiffTime
|
||||
timeOfDayField = Field{..}
|
||||
where
|
||||
precision :: Pico
|
||||
precision = MkFixed 1
|
||||
timeFormat :: String
|
||||
timeFormat = "%H:%M:%S%Q"
|
||||
|
||||
fieldEnctype = UrlEncoded
|
||||
fieldView theId name attrs val' isReq
|
||||
= [whamlet|
|
||||
$newline never
|
||||
<input id=#{theId} name=#{name} *{attrs} type=time step=#{showFixed True precision} :isReq:required value=#{val}>
|
||||
|]
|
||||
where val :: Text
|
||||
val = either id (pack . formatTime defaultTimeLocale timeFormat . nominalTimeToTimeOfDay) val'
|
||||
fieldParse = parseHelper $ \t
|
||||
-> case parseTimeM True defaultTimeLocale timeFormat (T.unpack t) of
|
||||
Just tod -> Right $ timeOfDayToNominalTime tod
|
||||
Nothing -> Left MsgInvalidTimeFormat
|
||||
|
||||
|
||||
|
||||
data SecretJSONFieldException = SecretJSONFieldDecryptFailure
|
||||
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic)
|
||||
@ -1544,7 +1501,7 @@ hoistField f Field{..} = Field
|
||||
}
|
||||
|
||||
prismAForm :: Monad m => Prism' s a -> Maybe s -> (Maybe a -> AForm m a) -> AForm m s
|
||||
-- ^ TODO: @Monad m => Prism' s a -> (Maybe a -> AForm m a) -> (Maybe s -> AForm m s)@
|
||||
-- ^ @Monad m => Prism' s a -> (Maybe a -> AForm m a) -> (Maybe s -> AForm m s)@
|
||||
prismAForm p outer form = review p <$> form inner
|
||||
where
|
||||
inner = outer >>= preview p
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
-- SPDX-FileCopyrightText: 2022-2024 Sarah Vaupel <sarah.vaupel@uniworx.de>, Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <sarah.vaupel@ifi.lmu.de>,Sarah Vaupel <vaupel.sarah@campus.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>,Wolfgang Witt <Wolfgang.Witt@campus.lmu.de>,Steffen Jost <s.jost@fraport.de>
|
||||
-- SPDX-FileCopyrightText: 2022-23 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <sarah.vaupel@ifi.lmu.de>,Sarah Vaupel <vaupel.sarah@campus.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>,Wolfgang Witt <Wolfgang.Witt@campus.lmu.de>,Steffen Jost <s.jost@fraport.de>
|
||||
--
|
||||
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@ -96,11 +96,6 @@ data Icon
|
||||
| IconFileUploadSession
|
||||
| IconStandaloneFieldError
|
||||
| IconFileUser
|
||||
| IconFastBackward
|
||||
| IconBackward
|
||||
| IconCurrent
|
||||
| IconForward
|
||||
| IconFastForward
|
||||
| IconPersonalIdentification
|
||||
| IconMenuWorkflows
|
||||
| IconVideo
|
||||
@ -199,11 +194,6 @@ iconText = \case
|
||||
IconFileUploadSession -> "file-upload"
|
||||
IconStandaloneFieldError -> "exclamation"
|
||||
IconFileUser -> "file-user"
|
||||
IconFastBackward -> "angle-double-left"
|
||||
IconBackward -> "angle-left"
|
||||
IconCurrent -> "circle"
|
||||
IconForward -> "angle-right"
|
||||
IconFastForward -> "angle-double-right"
|
||||
IconNotification -> "envelope"
|
||||
IconNotificationSent -> "envelope-open" -- "paper-plane", "shipping-fast", "hourglass-half"
|
||||
IconNoNotification -> "bell-slash"
|
||||
|
||||
@ -37,7 +37,6 @@ data GlobalGetParam = GetLang
|
||||
| GetError
|
||||
| GetSelectTable
|
||||
| GetGenerateToken
|
||||
| GetScheduleOptions
|
||||
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic)
|
||||
deriving anyclass (Universe, Finite)
|
||||
|
||||
|
||||
@ -1,128 +0,0 @@
|
||||
module Utils.Schedule
|
||||
( fetchCourseEventsScheduleInfo, fetchTutorialsScheduleInfo, fetchExamOccurrencesScheduleInfo
|
||||
, courseEventShouldBeDisplayedInSchedule, tutorialShouldBeDisplayedInSchedule, examOccurrenceShouldBeDisplayedInSchedule
|
||||
) where
|
||||
|
||||
import Import
|
||||
|
||||
import qualified Database.Esqueleto as E
|
||||
import qualified Database.Esqueleto.Utils as E
|
||||
|
||||
import Handler.Utils.Course (showCourseEventRoom)
|
||||
import Handler.Utils.Exam (showExamOccurrenceRoom)
|
||||
import Handler.Utils.Tutorial (showTutorialRoom)
|
||||
|
||||
import Utils.Course
|
||||
import Utils.Tutorial
|
||||
|
||||
import Utils.Schedule.Types
|
||||
|
||||
|
||||
fetchCourseEventsScheduleInfo :: MonadHandler m => Maybe UserId -> AuthTagActive -> UTCTime -> ReaderT SqlBackend m [ScheduleCourseEventInfo]
|
||||
fetchCourseEventsScheduleInfo muid ata now = E.select $ E.from $ \(course `E.InnerJoin` courseEvent) -> do
|
||||
E.on $ course E.^. CourseId E.==. courseEvent E.^. CourseEventCourse
|
||||
E.where_ $ courseEventShouldBeDisplayedInSchedule muid ata course courseEvent
|
||||
E.&&. mayViewCourse muid ata now course Nothing -- should not be necessary, but let's be on the safe side
|
||||
let showRoom = maybe E.false (flip showCourseEventRoom courseEvent . E.val) muid
|
||||
E.||. E.not_ (courseEvent E.^. CourseEventRoomHidden)
|
||||
return (course, courseEvent, showRoom)
|
||||
|
||||
fetchTutorialsScheduleInfo :: MonadHandler m => Maybe UserId -> AuthTagActive -> UTCTime -> ReaderT SqlBackend m [ScheduleTutorialInfo]
|
||||
fetchTutorialsScheduleInfo muid ata now = E.select $ E.from $ \(course `E.InnerJoin` tutorial) -> do
|
||||
E.on $ course E.^. CourseId E.==. tutorial E.^. TutorialCourse
|
||||
E.where_ $ tutorialShouldBeDisplayedInSchedule muid ata course tutorial
|
||||
E.&&. mayViewCourse muid ata now course Nothing -- should not be necessary, but let's be on the safe side
|
||||
let showRoom = maybe E.false (flip showTutorialRoom tutorial . E.val) muid
|
||||
E.||. E.not_ (tutorial E.^. TutorialRoomHidden)
|
||||
return (course, tutorial, showRoom)
|
||||
|
||||
fetchExamOccurrencesScheduleInfo :: MonadHandler m => Maybe UserId -> AuthTagActive -> UTCTime -> ReaderT SqlBackend m [ScheduleExamOccurrenceInfo]
|
||||
fetchExamOccurrencesScheduleInfo muid ata now = E.select $ E.from $ \(course `E.InnerJoin` exam `E.InnerJoin` examOccurrence) -> do
|
||||
E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse
|
||||
E.on $ exam E.^. ExamId E.==. examOccurrence E.^. ExamOccurrenceExam
|
||||
E.where_ $ examOccurrenceShouldBeDisplayedInSchedule muid ata now course exam examOccurrence
|
||||
let showRoom = maybe E.false (flip showExamOccurrenceRoom examOccurrence . E.val) muid
|
||||
E.||. E.not_ (examOccurrence E.^. ExamOccurrenceRoomHidden)
|
||||
return (course, exam, examOccurrence, showRoom)
|
||||
|
||||
|
||||
courseEventShouldBeDisplayedInSchedule :: Maybe UserId -> AuthTagActive -> E.SqlExpr (Entity Course) -> E.SqlExpr (Entity CourseEvent) -> E.SqlExpr (E.Value Bool)
|
||||
courseEventShouldBeDisplayedInSchedule muid@(Just uid) ata course courseEvent = E.exists . E.from $ \user ->
|
||||
let
|
||||
mCourseEventOpt = E.subSelect . E.from $ \courseEventScheduleOpt -> do
|
||||
E.where_ $ courseEventScheduleOpt E.^. CourseEventScheduleOptCourseEvent E.==. courseEvent E.^. CourseEventId
|
||||
E.&&. courseEventScheduleOpt E.^. CourseEventScheduleOptUser E.==. user E.^. UserId
|
||||
return $ courseEventScheduleOpt E.^. CourseEventScheduleOptOpt
|
||||
mCourseOpt = E.subSelect $ getCourseScheduleOpt course user
|
||||
in E.where_ $ user E.^. UserId E.==. E.val uid
|
||||
E.&&. E.fromMaybe
|
||||
( E.fromMaybe
|
||||
( user E.^. UserScheduleOccurrenceDisplayDefault
|
||||
E.&&. ( isCourseParticipant muid ata (course E.^. CourseId)
|
||||
E.||. isCourseLecturer muid ata (course E.^. CourseId)
|
||||
)
|
||||
)
|
||||
mCourseOpt
|
||||
)
|
||||
mCourseEventOpt
|
||||
courseEventShouldBeDisplayedInSchedule _ _ _ _ = E.false
|
||||
|
||||
tutorialShouldBeDisplayedInSchedule :: Maybe UserId -> AuthTagActive -> E.SqlExpr (Entity Course) -> E.SqlExpr (Entity Tutorial) -> E.SqlExpr (E.Value Bool)
|
||||
tutorialShouldBeDisplayedInSchedule muid@(Just uid) ata course tutorial = E.exists . E.from $ \user ->
|
||||
let
|
||||
mTutorialOpt = E.subSelect . E.from $ \tutorialScheduleOpt -> do
|
||||
E.where_ $ tutorialScheduleOpt E.^. TutorialScheduleOptTutorial E.==. tutorial E.^. TutorialId
|
||||
E.&&. tutorialScheduleOpt E.^. TutorialScheduleOptUser E.==. user E.^. UserId
|
||||
return $ tutorialScheduleOpt E.^. TutorialScheduleOptOpt
|
||||
mCourseOpt = E.subSelect $ getCourseScheduleOpt course user
|
||||
in E.where_ $ user E.^. UserId E.==. E.val uid
|
||||
E.&&. E.fromMaybe
|
||||
( ( E.fromMaybe
|
||||
(user E.^. UserScheduleOccurrenceDisplayDefault)
|
||||
mCourseOpt
|
||||
) E.&&. ( isTutorialTutor muid ata (tutorial E.^. TutorialId)
|
||||
E.||. isTutorialParticipant muid ata (tutorial E.^. TutorialId)
|
||||
)
|
||||
)
|
||||
mTutorialOpt
|
||||
tutorialShouldBeDisplayedInSchedule _ _ _ _ = E.false
|
||||
|
||||
examOccurrenceShouldBeDisplayedInSchedule :: Maybe UserId -> AuthTagActive -> UTCTime -> E.SqlExpr (Entity Course) -> E.SqlExpr (Entity Exam) -> E.SqlExpr (Entity ExamOccurrence) -> E.SqlExpr (E.Value Bool)
|
||||
examOccurrenceShouldBeDisplayedInSchedule muid@(Just uid) ata now course exam examOcc = E.exists . E.from $ \user ->
|
||||
let
|
||||
mExamOccOpt = E.subSelect . E.from $ \examOccScheduleOpt -> do
|
||||
E.where_ $ examOccScheduleOpt E.^. ExamOccurrenceScheduleOptExamOccurrence E.==. examOcc E.^. ExamOccurrenceId
|
||||
E.&&. examOccScheduleOpt E.^. ExamOccurrenceScheduleOptUser E.==. user E.^. UserId
|
||||
return $ examOccScheduleOpt E.^. ExamOccurrenceScheduleOptOpt
|
||||
|
||||
mCourseOpt = E.subSelect $ getCourseScheduleOpt course user
|
||||
in E.where_ $ user E.^. UserId E.==. E.val uid
|
||||
E.&&. E.fromMaybe
|
||||
( ( E.fromMaybe
|
||||
(user E.^. UserScheduleOccurrenceDisplayDefault)
|
||||
mCourseOpt
|
||||
) E.&&. ( isCourseLecturer muid ata (course E.^. CourseId)
|
||||
E.||. ( mayViewCourse muid ata now course Nothing -- do NOT remove, this is actually necessary here!
|
||||
-- (There can be exam participants that are
|
||||
-- not enrolled, me thinks)
|
||||
E.&&. E.maybe E.false (\visibleFrom -> visibleFrom E.<=. E.val now) (exam E.^. ExamVisibleFrom) -- is the exam visible?
|
||||
E.&&. E.maybe E.false (\publishOcc -> publishOcc E.<=. E.val now) (exam E.^. ExamPublishOccurrenceAssignments) -- are the exam occurrence assignments visible?
|
||||
E.&&. (E.exists $ E.from $ \examRegistration -> E.where_ $
|
||||
examRegistration E.^. ExamRegistrationExam E.==. exam E.^. ExamId
|
||||
E.&&. E.just (examRegistration E.^. ExamRegistrationUser) E.==. E.val muid
|
||||
E.&&. E.maybe E.true (\registrationOccurrence -> E.maybe E.false (const E.true) mExamOccOpt E.||. registrationOccurrence E.==. examOcc E.^. ExamOccurrenceId) (examRegistration E.^. ExamRegistrationOccurrence) -- if registered for a specific occurrence, get only this one and occurrences with an opt-in, otherwise get every occurrence available
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
mExamOccOpt
|
||||
examOccurrenceShouldBeDisplayedInSchedule _ _ _ _ _ _ = E.false
|
||||
|
||||
|
||||
-- Local helper functions
|
||||
|
||||
getCourseScheduleOpt :: E.SqlExpr (Entity Course) -> E.SqlExpr (Entity User) -> E.SqlQuery (E.SqlExpr (E.Value Bool))
|
||||
getCourseScheduleOpt course user = E.from $ \courseScheduleOpt -> do
|
||||
E.where_ $ courseScheduleOpt E.^. CourseScheduleOptCourse E.==. course E.^. CourseId
|
||||
E.&&. courseScheduleOpt E.^. CourseScheduleOptUser E.==. user E.^. UserId
|
||||
return $ courseScheduleOpt E.^. CourseScheduleOptOpt
|
||||
@ -1,37 +0,0 @@
|
||||
module Utils.Schedule.Types
|
||||
( module Utils.Schedule.Types
|
||||
) where
|
||||
|
||||
import Import
|
||||
|
||||
import qualified Database.Esqueleto as E
|
||||
|
||||
import Utils.Schedule.Types.ScheduleEntry as Utils.Schedule.Types
|
||||
import Utils.Schedule.Types.ScheduleView as Utils.Schedule.Types
|
||||
import Utils.Schedule.Types.ScheduleOffset as Utils.Schedule.Types
|
||||
import Utils.Schedule.Types.ScheduleOptions as Utils.Schedule.Types
|
||||
|
||||
|
||||
-- TODO: replace Info types with one joined type and fetch info in one single runDB
|
||||
|
||||
type ScheduleCourseEventInfo = ( Entity Course
|
||||
, Entity CourseEvent
|
||||
, E.Value Bool -- ^ showRoom
|
||||
)
|
||||
|
||||
type ScheduleTutorialInfo = ( Entity Course
|
||||
, Entity Tutorial
|
||||
, E.Value Bool -- ^ showRoom
|
||||
)
|
||||
|
||||
type ScheduleExamOccurrenceInfo = ( Entity Course
|
||||
, Entity Exam
|
||||
, Entity ExamOccurrence
|
||||
, E.Value Bool -- ^ showRoom
|
||||
)
|
||||
type ScheduleExamOccurrenceJoinedInfo = ( Entity Course
|
||||
, Entity Exam
|
||||
, NonEmpty ( Entity ExamOccurrence
|
||||
, E.Value Bool -- ^ showRoom
|
||||
)
|
||||
)
|
||||
@ -1,34 +0,0 @@
|
||||
module Utils.Schedule.Types.ScheduleEntry
|
||||
( ScheduleEntry(..)
|
||||
) where
|
||||
|
||||
import Import
|
||||
|
||||
|
||||
data ScheduleEntry = ScheduleCourseEvent
|
||||
{ sceCourse :: Entity Course
|
||||
, sceType :: CourseEventType
|
||||
, sceRoom :: Maybe RoomReference
|
||||
, sceShowRoom :: Bool
|
||||
, sceOccurrence :: Either OccurrenceException OccurrenceSchedule
|
||||
, sceNoOccur :: Set LocalTime
|
||||
, sceTerm :: Entity Term
|
||||
}
|
||||
| ScheduleTutorial
|
||||
{ stCourse :: Entity Course
|
||||
, stName :: TutorialName
|
||||
, stType :: TutorialType
|
||||
, stRoom :: Maybe RoomReference
|
||||
, stShowRoom :: Bool
|
||||
, stOccurrence :: Either OccurrenceException OccurrenceSchedule
|
||||
, stNoOccur :: Set LocalTime
|
||||
, stTerm :: Entity Term
|
||||
}
|
||||
| ScheduleExamOccurrence
|
||||
{ seoCourse :: Entity Course
|
||||
, seoExamName :: ExamName
|
||||
, seoRooms :: Set (Maybe RoomReference, Bool)
|
||||
, seoStart :: UTCTime
|
||||
, seoEnd :: Maybe UTCTime
|
||||
}
|
||||
deriving (Generic, Typeable)
|
||||
@ -1,25 +0,0 @@
|
||||
module Utils.Schedule.Types.ScheduleOffset
|
||||
( ScheduleOffset(..)
|
||||
, addOffset, offsetInDays
|
||||
) where
|
||||
|
||||
import Import.NoModel
|
||||
|
||||
|
||||
data ScheduleOffset = ScheduleOffsetNone
|
||||
| ScheduleOffsetDays Int
|
||||
deriving (Eq, Ord, Show, Read, Generic, Typeable)
|
||||
|
||||
derivePathPiece ''ScheduleOffset (camelToPathPiece' 1) "_"
|
||||
|
||||
|
||||
-- | Join two ScheduleOffsets by addition
|
||||
addOffset :: ScheduleOffset -> ScheduleOffset -> ScheduleOffset
|
||||
addOffset ScheduleOffsetNone offset = offset
|
||||
addOffset offset ScheduleOffsetNone = offset
|
||||
addOffset (ScheduleOffsetDays d) (ScheduleOffsetDays d') = ScheduleOffsetDays $ d + d'
|
||||
|
||||
-- | Calculate number of offset days from ScheduleOffset
|
||||
offsetInDays :: ScheduleOffset -> Int
|
||||
offsetInDays ScheduleOffsetNone = 0
|
||||
offsetInDays (ScheduleOffsetDays d) = d
|
||||
@ -1,45 +0,0 @@
|
||||
module Utils.Schedule.Types.ScheduleOptions
|
||||
( ScheduleOffset(..)
|
||||
, ScheduleOptionsAction(..)
|
||||
, ScheduleOptions(..)
|
||||
) where
|
||||
|
||||
import Import
|
||||
|
||||
import Utils.Form
|
||||
|
||||
import Utils.Schedule.Types.ScheduleOffset
|
||||
import Utils.Schedule.Types.ScheduleView
|
||||
|
||||
|
||||
data ScheduleOptionsAction = ScheduleSetView
|
||||
| ScheduleSetOffset ScheduleOffset
|
||||
| ScheduleSetDefault
|
||||
deriving (Eq, Ord, Show, Read, Generic, Typeable)
|
||||
|
||||
derivePathPiece ''ScheduleOptionsAction (camelToPathPiece' 1) "--"
|
||||
|
||||
|
||||
data ScheduleOptions = ScheduleOptions
|
||||
{ scheduleView :: ScheduleView
|
||||
, scheduleOffset :: ScheduleOffset
|
||||
, scheduleOptionsAction :: ScheduleOptionsAction
|
||||
}
|
||||
deriving (Eq, Ord, Show, Read, Generic, Typeable)
|
||||
|
||||
derivePathPiece ''ScheduleOptions (camelToPathPiece' 1) "---"
|
||||
|
||||
instance Button UniWorX ScheduleOptions where
|
||||
btnClasses = const [BCIsButton]
|
||||
btnLabel ScheduleOptions{..} = case scheduleOptionsAction of
|
||||
ScheduleSetDefault -> i18n MsgScheduleReset
|
||||
ScheduleSetView -> case scheduleView of
|
||||
ScheduleViewWeek -> i18n MsgScheduleViewWeek
|
||||
ScheduleSetOffset o -> case scheduleView of
|
||||
ScheduleViewWeek -> let iconTooltipMessage i m = iconTooltip (i18n m) (Just i) True
|
||||
o' = offsetInDays o
|
||||
in if | o' <= (-7) -> iconTooltipMessage IconFastBackward MsgScheduleOffsetWeekBackwardWeek
|
||||
| o' < 0 -> iconTooltipMessage IconBackward . MsgScheduleOffsetWeekBackwardDays $ abs o'
|
||||
| o' == 0 -> iconTooltipMessage IconCurrent MsgScheduleOffsetWeekCurrent
|
||||
| o' < 7 -> iconTooltipMessage IconForward . MsgScheduleOffsetWeekForwardDays $ abs o'
|
||||
| otherwise -> iconTooltipMessage IconFastForward MsgScheduleOffsetWeekForwardWeek
|
||||
@ -1,23 +0,0 @@
|
||||
module Utils.Schedule.Types.ScheduleView
|
||||
( ScheduleView(..)
|
||||
) where
|
||||
|
||||
import Import.NoModel
|
||||
|
||||
import Model.Types.TH.PathPiece
|
||||
|
||||
|
||||
-- TODO: implement ScheduleViewDay and ScheduleViewMonth
|
||||
data ScheduleView = ScheduleViewWeek
|
||||
deriving (Eq, Ord, Enum, Show, Read, Generic, Typeable, NFData)
|
||||
|
||||
instance Bounded ScheduleView where
|
||||
minBound = maxBound
|
||||
maxBound = ScheduleViewWeek
|
||||
instance Finite ScheduleView
|
||||
instance Universe ScheduleView
|
||||
|
||||
nullaryPathPiece ''ScheduleView $ camelToPathPiece' 2
|
||||
pathPieceJSON ''ScheduleView
|
||||
pathPieceJSONKey ''ScheduleView
|
||||
derivePersistFieldPathPiece ''ScheduleView
|
||||
@ -1,261 +0,0 @@
|
||||
module Utils.Schedule.Week
|
||||
( weekOffsets, weekSchedule
|
||||
) where
|
||||
|
||||
import Import
|
||||
|
||||
import qualified Data.CaseInsensitive as CI
|
||||
import qualified Data.Map as Map
|
||||
import qualified Data.Set as Set
|
||||
|
||||
import qualified Database.Esqueleto as E
|
||||
|
||||
import Handler.Utils.DateTime (formatTimeRangeW, formatTimeW, utcToLocalTime, localTimeToUTCSimple)
|
||||
import Handler.Utils.Term (fetchActiveTerms)
|
||||
import Handler.Utils.Widgets (roomReferenceWidget)
|
||||
|
||||
import Utils.Schedule
|
||||
import Utils.Schedule.Types
|
||||
|
||||
import Utils.Schedule.Week.SlotAssociation
|
||||
import Utils.Schedule.Week.TimeSlot
|
||||
|
||||
|
||||
weekOffsets :: UTCTime -> Entity User -> ScheduleOffset -> [ScheduleOffset]
|
||||
weekOffsets now user@(Entity _ User{userScheduleWeekDays = ScheduleWeekDays userScheduleWeekDays}) scheduleOffset
|
||||
= nub [ ScheduleOffsetDays (-7)
|
||||
, ScheduleOffsetDays $ go (-1)
|
||||
, ScheduleOffsetNone
|
||||
, ScheduleOffsetDays $ go 1
|
||||
, ScheduleOffsetDays 7
|
||||
]
|
||||
where
|
||||
go d
|
||||
| weeksEqual 0 d
|
||||
, abs d < 7
|
||||
= go d'
|
||||
| d >= 0
|
||||
, abs d < 7
|
||||
, weeksEqual d d'
|
||||
= go d'
|
||||
| otherwise
|
||||
= d
|
||||
where d' = bool pred succ (d >= 0) d
|
||||
weeksEqual = on (==) $ filter (\d' -> dayOfWeek d' `elem` userScheduleWeekDays) . week
|
||||
where week d = weekDays now user $ ScheduleOffsetDays d `addOffset` scheduleOffset
|
||||
|
||||
|
||||
weekSchedule :: UTCTime -> Entity User -> ScheduleOffset -> Widget
|
||||
weekSchedule now user@(Entity uid User{userScheduleWeekDays = ScheduleWeekDays userScheduleWeekDays, ..}) scheduleOffset = do
|
||||
ata <- getSessionActiveAuthTags
|
||||
let localNow = utcToLocalTime now
|
||||
|
||||
let
|
||||
dayNowOffset = toInteger (offsetInDays scheduleOffset) `addDays` localDay (utcToLocalTime now)
|
||||
week = weekDays now user scheduleOffset
|
||||
lectureDay (Entity _ Term{..}) d = termLectureStart <= d && d <= termLectureEnd
|
||||
&& d `notElem` termHolidays
|
||||
isToday d = d == localDay localNow
|
||||
isCurrentSlot = isInTimeSlot $ localTimeOfDay localNow
|
||||
isCurrentScheduleEntry d ts = \case
|
||||
ScheduleCourseEvent{sceOccurrence,sceNoOccur} -> not (localNow `Set.member` sceNoOccur) && case sceOccurrence of
|
||||
Left ExceptNoOccur{..} -> localNow /= exceptTime -- TODO: sceNoOccur and Left ExceptNoOccur{..} seem to be the same case
|
||||
Left ExceptOccur{..} -> d == exceptDay && timeOfDayToUTC exceptStart <= now && now < timeOfDayToUTC exceptEnd
|
||||
Right ScheduleWeekly{..} -> scheduleDayOfWeek == dayOfWeek d && timeOfDayToUTC scheduleStart <= now && now < timeOfDayToUTC scheduleEnd
|
||||
ScheduleTutorial{stOccurrence,stNoOccur} -> not (localNow `Set.member` stNoOccur) && case stOccurrence of
|
||||
Left ExceptNoOccur{..} -> localNow /= exceptTime -- TODO: sceNoOccur and Left ExceptNoOccur{..} seem to be the same case
|
||||
Left ExceptOccur{..} -> d == exceptDay && timeOfDayToUTC exceptStart <= now && now < timeOfDayToUTC exceptEnd
|
||||
Right ScheduleWeekly{..} -> scheduleDayOfWeek == dayOfWeek d && timeOfDayToUTC scheduleStart <= now && now < timeOfDayToUTC scheduleEnd
|
||||
ScheduleExamOccurrence{seoStart,seoEnd} -> seoStart <= now && now < (fromMaybe (view _2 $ timeSlotToUTCTime d ts) seoEnd)
|
||||
where
|
||||
timeOfDayToUTC = localTimeToUTCSimple . LocalTime d
|
||||
|
||||
(activeTerms, courseEvents, tutorials, examOccurrences) <- liftHandler . runDB $ (,,,)
|
||||
<$> fetchActiveTerms
|
||||
-- TODO: fetch course events for this week only?
|
||||
<*> fetchCourseEventsScheduleInfo (Just uid) ata now
|
||||
<*> fetchTutorialsScheduleInfo (Just uid) ata now
|
||||
-- TODO: this makes the exam table partly redundant => maybe remove?
|
||||
<*> fetchExamOccurrencesScheduleInfo (Just uid) ata now
|
||||
|
||||
let
|
||||
holidays = concatMap (termHolidays . entityVal) activeTerms
|
||||
|
||||
courseEventToScheduleEntries :: ScheduleCourseEventInfo -> [ScheduleEntry]
|
||||
courseEventToScheduleEntries (sceCourse@(Entity _ Course{..}), Entity _ CourseEvent{courseEventType=sceType,courseEventRoom=sceRoom,courseEventTime=Occurrences{..}}, E.Value sceShowRoom)
|
||||
| [sceTerm] <- filter ((== courseTerm) . entityKey) activeTerms
|
||||
, termActive $ entityVal sceTerm
|
||||
= let scheduleds
|
||||
= Set.toList occurrencesScheduled <&> \scheduled ->
|
||||
let sceOccurrence = Right scheduled in ScheduleCourseEvent{..}
|
||||
exceptions
|
||||
= Set.toList occurrencesExceptions <&> \exception ->
|
||||
let sceOccurrence = Left exception in ScheduleCourseEvent{..}
|
||||
sceNoOccur = setOf (folded . _ExceptNoOccur) occurrencesExceptions
|
||||
in scheduleds <> exceptions
|
||||
| otherwise = mempty
|
||||
|
||||
tutorialToScheduleEntries :: ScheduleTutorialInfo -> [ScheduleEntry]
|
||||
tutorialToScheduleEntries (stCourse@(Entity _ Course{..}), Entity _ Tutorial{tutorialName=stName,tutorialType=stType,tutorialRoom=stRoom,tutorialTime=Occurrences{..}}, E.Value stShowRoom)
|
||||
| [stTerm] <- filter ((== courseTerm) . entityKey) activeTerms
|
||||
, termActive $ entityVal stTerm
|
||||
= let scheduleds
|
||||
= Set.toList occurrencesScheduled <&> \scheduled ->
|
||||
let stOccurrence = Right scheduled in ScheduleTutorial{..}
|
||||
exceptions
|
||||
= Set.toList occurrencesExceptions <&> \exception ->
|
||||
let stOccurrence = Left exception in ScheduleTutorial{..}
|
||||
stNoOccur = setOf (folded . _ExceptNoOccur) occurrencesExceptions
|
||||
in scheduleds <> exceptions
|
||||
| otherwise
|
||||
= mempty
|
||||
|
||||
joinParallelExamOccurrences :: [ScheduleExamOccurrenceInfo] -> [ScheduleExamOccurrenceJoinedInfo]
|
||||
joinParallelExamOccurrences = go [] where
|
||||
go acc [] = acc
|
||||
go acc (examOcc@(course, exam, occ, showRoom):examOccs) =
|
||||
let ((((\(_,_,o,s) -> (o,s)) <$>) -> parallel), other) = partition (examOcc `isParallelTo`) examOccs
|
||||
in go ((course, exam, (occ,showRoom):|parallel):acc) other
|
||||
(Entity cid _, Entity eid _, Entity _ occ, _) `isParallelTo` (Entity cid' _, Entity eid' _, Entity _ occ', _) =
|
||||
cid == cid' && eid == eid'
|
||||
&& examOccurrenceStart occ == examOccurrenceStart occ' && examOccurrenceEnd occ == examOccurrenceEnd occ'
|
||||
|
||||
examOccurrenceToScheduleEntry :: ScheduleExamOccurrenceJoinedInfo -> ScheduleEntry
|
||||
examOccurrenceToScheduleEntry (seoCourse@(Entity _ Course{}), Entity _ Exam{examName=seoExamName}, examOccs@((Entity _ occ, _):|_)) =
|
||||
let seoRooms = (Set.fromList . toList) $ (\(Entity _ ExamOccurrence{examOccurrenceRoom}, E.Value showRoom) -> (examOccurrenceRoom, showRoom)) <$> examOccs
|
||||
seoStart = examOccurrenceStart occ -- multiple exam occurrences are joined on equality of start and end,
|
||||
seoEnd = examOccurrenceEnd occ -- so taking the timestamps of the first occurrence suffices
|
||||
in ScheduleExamOccurrence{..}
|
||||
|
||||
events'' :: Map Day (Map TimeSlot [(ScheduleEntry, SlotAssociation)])
|
||||
events'' = Map.fromList $ week <&> \d ->
|
||||
( d
|
||||
, Map.fromList $ allTimeSlots <&> \slot ->
|
||||
( slot
|
||||
, mapMaybe (\entry -> (entry, ) <$> seIsInSlot d slot entry) scheduleEntries
|
||||
)
|
||||
) where
|
||||
scheduleEntries = join $ (courseEventToScheduleEntries <$> courseEvents)
|
||||
<> (tutorialToScheduleEntries <$> tutorials)
|
||||
<> pure (examOccurrenceToScheduleEntry <$> (joinParallelExamOccurrences examOccurrences))
|
||||
|
||||
events' :: Map Day (Map TimeSlot [(ScheduleEntry, SlotAssociation)])
|
||||
events' = flip imap events'' $ \currentDay slotsPerDay -> slotsPerDay <&> \occurrencesInSlot ->
|
||||
let
|
||||
isRegularWithoutException :: ScheduleEntry -> Bool
|
||||
isRegularWithoutException =
|
||||
let -- remove regular occurrences if there is a NoOccur exception for the occurrence of this week
|
||||
goPrune noOccurs term = \case
|
||||
Right ScheduleWeekly{..} -> and
|
||||
[ lectureDay term currentDay
|
||||
, flip none noOccurs $
|
||||
\needle -> let localDay = scheduleDayOfWeek `dayOfWeekToDayWith` dayNowOffset
|
||||
in LocalTime{ localDay, localTimeOfDay = scheduleStart } <= needle
|
||||
&& needle <= LocalTime{ localDay, localTimeOfDay = scheduleEnd }
|
||||
]
|
||||
Left ExceptOccur{} -> True
|
||||
-- remove NoOccur exceptions
|
||||
Left ExceptNoOccur{} -> False
|
||||
in \case
|
||||
ScheduleCourseEvent{..} -> goPrune sceNoOccur sceTerm sceOccurrence
|
||||
ScheduleTutorial{..} -> goPrune stNoOccur stTerm stOccurrence
|
||||
_ -> True
|
||||
in sortOn (views _1 $ scheduleEntryStartUTC currentDay) $ filter (views _1 isRegularWithoutException) occurrencesInSlot
|
||||
|
||||
-- TODO: perform this filtering asap, in DB fetch if possible
|
||||
events :: Map Day (Map TimeSlot [(ScheduleEntry, SlotAssociation)])
|
||||
events = Map.filterWithKey shouldBeDisplayedOrHasEvents events' where
|
||||
shouldBeDisplayedOrHasEvents d entries = dayOfWeek d `elem` userScheduleWeekDays || any (not . null) entries
|
||||
|
||||
timeSlotsDefaultDisplay :: Set TimeSlot
|
||||
timeSlotsDefaultDisplay = Set.fromList $ timeSlotsFromTo userScheduleWeekTimeslotLength userScheduleWeekTimeFrom userScheduleWeekTimeTo
|
||||
|
||||
allTimeSlots :: [TimeSlot]
|
||||
allTimeSlots = timeSlotsAll userScheduleWeekTimeslotLength userScheduleWeekTimeFrom
|
||||
|
||||
timeSlotIsEmpty :: TimeSlot -> Bool
|
||||
timeSlotIsEmpty slot = foldr (\d acc -> acc && maybe True null (d Map.!? slot)) True events
|
||||
|
||||
$(widgetFile "schedule/week")
|
||||
|
||||
|
||||
-- Local helper functions
|
||||
|
||||
-- | Get days that are to be displayed in the week schedule
|
||||
weekDays :: UTCTime -> Entity User -> ScheduleOffset -> [Day]
|
||||
weekDays now (Entity _ User{userWeekStart}) scheduleOffset = go dayNowOffset
|
||||
where go d
|
||||
| dayOfWeek d == firstDay = [d .. addDays 6 d]
|
||||
| otherwise = go $ pred d
|
||||
firstDay = toEnum $ fromEnum userWeekStart + offsetInDays scheduleOffset
|
||||
dayNowOffset = toInteger (offsetInDays scheduleOffset) `addDays` localDay (utcToLocalTime now)
|
||||
|
||||
-- | Check whether a given ScheduleEntry lies in a given TimeSlot
|
||||
seIsInSlot :: Day -> TimeSlot -> ScheduleEntry -> Maybe SlotAssociation
|
||||
seIsInSlot d slot = \case
|
||||
ScheduleCourseEvent{sceOccurrence} -> occurrenceIsInSlot sceOccurrence
|
||||
ScheduleTutorial{stOccurrence} -> occurrenceIsInSlot stOccurrence
|
||||
ScheduleExamOccurrence{seoStart, seoEnd = Nothing}
|
||||
-> let associated = slotBegin <= seoStart && seoStart < slotEnd
|
||||
in guardOn associated SlotBegins
|
||||
ScheduleExamOccurrence{seoStart, seoEnd = Just seoEnd}
|
||||
-> let associated = seoEnd > slotBegin && seoStart < slotEnd
|
||||
in guardOn associated $ _SlotAssociation # ( slotBegin <= seoStart && seoStart < slotEnd
|
||||
, slotBegin <= seoEnd && seoEnd <= slotEnd
|
||||
)
|
||||
where
|
||||
(slotBegin, slotEnd) = timeSlotToUTCTime d slot
|
||||
occurrenceIsInSlot occurrence = guardOn associated $ _SlotAssociation # ( slotBegin <= occStart && occStart < slotEnd
|
||||
, slotBegin <= occEnd && occEnd <= slotEnd
|
||||
)
|
||||
where
|
||||
associated = occEnd > slotBegin && occStart < slotEnd
|
||||
occStart = localTimeToUTCSimple $ LocalTime occDay occStartTime
|
||||
occEnd = localTimeToUTCSimple $ LocalTime occDay occEndTime
|
||||
(occDay, occStartTime, occEndTime) = case occurrence of
|
||||
Right ScheduleWeekly{..} -> (scheduleDayOfWeek `dayOfWeekToDayWith` d, scheduleStart, scheduleEnd)
|
||||
Left ExceptOccur{..} -> (exceptDay, exceptStart, exceptEnd)
|
||||
Left ExceptNoOccur{exceptTime=LocalTime{..}} -> (localDay, localTimeOfDay, localTimeOfDay)
|
||||
|
||||
-- | To which route should each schedule entry link to?
|
||||
scheduleEntryToHref :: ScheduleEntry -> Route UniWorX
|
||||
scheduleEntryToHref = \case
|
||||
ScheduleCourseEvent{sceCourse=(Entity _ Course{..})} -> CourseR courseTerm courseSchool courseShorthand CShowR -- TODO: link to events table? (currently has no id)
|
||||
ScheduleTutorial{stCourse=(Entity _ Course{..})} -> CourseR courseTerm courseSchool courseShorthand CShowR -- TODO: link to table with id "tutorials"?
|
||||
ScheduleExamOccurrence{seoCourse=(Entity _ Course{..}),seoExamName} -> CExamR courseTerm courseSchool courseShorthand seoExamName EShowR
|
||||
|
||||
-- | At which UTCTime does a ScheduleEntry start, given a specific day?
|
||||
scheduleEntryStartUTC :: Day -> ScheduleEntry -> UTCTime
|
||||
scheduleEntryStartUTC currentDay = \case
|
||||
ScheduleCourseEvent{sceOccurrence} -> occurrenceToStart sceOccurrence
|
||||
ScheduleTutorial{stOccurrence} -> occurrenceToStart stOccurrence
|
||||
ScheduleExamOccurrence{seoStart} -> seoStart
|
||||
where
|
||||
occurrenceToStart = \case
|
||||
Left ExceptOccur{exceptDay, exceptStart} -> localTimeToUTCSimple $ LocalTime exceptDay exceptStart
|
||||
Left ExceptNoOccur{exceptTime} -> localTimeToUTCSimple exceptTime
|
||||
Right ScheduleWeekly{scheduleStart} -> localTimeToUTCSimple $ LocalTime currentDay scheduleStart
|
||||
|
||||
-- | Calls formatTimeRangeW with the correct arguments and prepends an occurrence descriptor based on the occurrence type
|
||||
formatEitherOccurrenceW :: Either OccurrenceException OccurrenceSchedule -> Widget
|
||||
formatEitherOccurrenceW = \case
|
||||
Right ScheduleWeekly{..} -> [whamlet| _{MsgScheduleTime}: |] <> formatTimeRangeW SelFormatTime scheduleStart (Just scheduleEnd)
|
||||
Left ExceptOccur{..} -> [whamlet| _{MsgScheduleOccur}: |] <> formatTimeRangeW SelFormatTime (LocalTime exceptDay exceptStart) (Just $ LocalTime exceptDay exceptEnd)
|
||||
Left ExceptNoOccur{} -> [whamlet| _{MsgScheduleNoOccur} |] -- <> formatTimeW SelFormatDateTime exceptTime
|
||||
|
||||
-- | Uniquely identify each day as table head
|
||||
-- | This avoids constantly hiding e.g. some DayOfWeek (which would interfere with day offsets)
|
||||
dayTableHeadIdent :: Day -> Text
|
||||
dayTableHeadIdent = tshow . toModifiedJulianDay
|
||||
|
||||
-- | Convert from DayOfWeek to Day of this week using the current day
|
||||
dayOfWeekToDayWith :: DayOfWeek -> Day -> Day
|
||||
dayOfWeekToDayWith weekDay = go where
|
||||
go d | weekDay' == weekDay = d
|
||||
| weekDay' > weekDay = go $ pred d
|
||||
| otherwise = go $ succ d
|
||||
where weekDay' = dayOfWeek d
|
||||
|
||||
-- | Auxiliary definition to be used in templates since ranges are not parsed correctly
|
||||
indexedList :: [a] -> [(Int, a)]
|
||||
indexedList = zip [0..]
|
||||
@ -1,34 +0,0 @@
|
||||
module Utils.Schedule.Week.SlotAssociation
|
||||
( SlotAssociation(..)
|
||||
, _SlotAssociation
|
||||
, slotAssocIsCont
|
||||
) where
|
||||
|
||||
import Import
|
||||
|
||||
|
||||
data SlotAssociation
|
||||
= SlotIntersects -- ^ Slot is true subset of event
|
||||
| SlotEnds -- ^ Event ends in slot, but does not begin within
|
||||
| SlotBegins -- ^ Event begins in slot, but does not end within
|
||||
| SlotContained -- ^ Event starts and ends within slot
|
||||
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
|
||||
deriving anyclass (Universe, Finite)
|
||||
nullaryPathPiece ''SlotAssociation $ camelToPathPiece' 1
|
||||
|
||||
_SlotAssociation :: Iso' SlotAssociation (Bool, Bool)
|
||||
_SlotAssociation = iso toBools fromBools
|
||||
where
|
||||
toBools = \case
|
||||
SlotIntersects -> (False, False)
|
||||
SlotEnds -> (False, True )
|
||||
SlotBegins -> (True, False)
|
||||
SlotContained -> (True, True )
|
||||
fromBools = \case
|
||||
(False, False) -> SlotIntersects
|
||||
(False, True ) -> SlotEnds
|
||||
(True, False) -> SlotBegins
|
||||
(True, True ) -> SlotContained
|
||||
|
||||
slotAssocIsCont :: SlotAssociation -> Bool
|
||||
slotAssocIsCont = views (_SlotAssociation . _1) not
|
||||
@ -1,67 +0,0 @@
|
||||
module Utils.Schedule.Week.TimeSlot
|
||||
( TimeSlot(..)
|
||||
, timeSlots, timeSlotsFromTo, timeSlotsAll
|
||||
, isInTimeSlot
|
||||
, nextTimeSlot
|
||||
, timeSlotToUTCTime
|
||||
, formatTimeSlotW
|
||||
) where
|
||||
|
||||
import Import
|
||||
|
||||
import Handler.Utils.DateTime (formatTimeRangeW, localTimeToUTCSimple)
|
||||
|
||||
import Utils.Schedule.Week.Types.TimeSlot
|
||||
|
||||
|
||||
-- TODO: This module needs major refactoring
|
||||
|
||||
|
||||
timeSlots :: Bool -- ^ Only slots between from/to?
|
||||
-> NominalDiffTime -- ^ Step
|
||||
-> NominalDiffTime -- ^ From
|
||||
-> NominalDiffTime -- ^ To
|
||||
-> [TimeSlot]
|
||||
timeSlots onlyFromTo (abs -> slotStep) f t
|
||||
| t < f = timeSlots onlyFromTo slotStep t f
|
||||
| slotStep <= 0 = error "Invalid slotStep"
|
||||
| otherwise = reverse [ TimeSlot{..}
|
||||
| tsTo <- [f,f - slotStep..0]
|
||||
, let tsFrom = tsTo - slotStep
|
||||
, not onlyFromTo || tsFrom >= f
|
||||
, tsFrom >= 0
|
||||
]
|
||||
++ [ TimeSlot{..}
|
||||
| tsFrom <- [f,f + slotStep..nominalDay]
|
||||
, let tsTo = tsFrom + slotStep
|
||||
, not onlyFromTo || tsTo <= t
|
||||
, tsTo <= nominalDay
|
||||
]
|
||||
|
||||
timeSlotsFromTo :: NominalDiffTime -- ^ Step
|
||||
-> NominalDiffTime -- ^ From
|
||||
-> NominalDiffTime -- ^ To
|
||||
-> [TimeSlot]
|
||||
timeSlotsFromTo = timeSlots True
|
||||
|
||||
timeSlotsAll :: NominalDiffTime -- ^ Step
|
||||
-> NominalDiffTime -- ^ From
|
||||
-> [TimeSlot]
|
||||
timeSlotsAll step f = timeSlots False step f f -- @t@ is unused in `timeSlots`, iff @onlyFromTo@ is `False`
|
||||
|
||||
-- | Check whether a given time of day lies within a given TimeSlot
|
||||
isInTimeSlot :: TimeOfDay -> TimeSlot -> Bool
|
||||
isInTimeSlot (timeOfDayToNominalTime -> time) TimeSlot{..} = tsFrom <= time && time < tsTo
|
||||
|
||||
-- | Get the successor of a TimeSlot
|
||||
nextTimeSlot :: NominalDiffTime -> TimeSlot -> TimeSlot
|
||||
nextTimeSlot slotStep TimeSlot{..} = TimeSlot{ tsFrom = tsTo, tsTo = tsTo + slotStep }
|
||||
|
||||
-- | Convert a TimeSlot to UTCTime for a given TimeZone
|
||||
timeSlotToUTCTime :: Day -> TimeSlot -> (UTCTime, UTCTime)
|
||||
timeSlotToUTCTime d TimeSlot{..} = (nominalDiffTimeToUTC tsFrom, nominalDiffTimeToUTC tsTo)
|
||||
where nominalDiffTimeToUTC = localTimeToUTCSimple . LocalTime d . nominalTimeToTimeOfDay
|
||||
|
||||
-- | Format a given TimeSlot as time range
|
||||
formatTimeSlotW :: TimeSlot -> Widget
|
||||
formatTimeSlotW TimeSlot{..} = formatTimeRangeW SelFormatTime (nominalTimeToTimeOfDay tsFrom) $ Just (nominalTimeToTimeOfDay tsTo)
|
||||
@ -1,5 +0,0 @@
|
||||
module Utils.Schedule.Week.Types
|
||||
( module Utils.Schedule.Week.Types
|
||||
) where
|
||||
|
||||
import Utils.Schedule.Week.Types.TimeSlot as Utils.Schedule.Week.Types
|
||||
@ -1,14 +0,0 @@
|
||||
module Utils.Schedule.Week.Types.TimeSlot
|
||||
( TimeSlot(..)
|
||||
) where
|
||||
|
||||
import Import.NoModel
|
||||
|
||||
|
||||
-- | Half-open interval of time
|
||||
--
|
||||
-- Fields are to be interpreted as time since midnight
|
||||
data TimeSlot = TimeSlot
|
||||
{ tsFrom :: NominalDiffTime -- ^ Inclusive
|
||||
, tsTo :: NominalDiffTime -- ^ Exclusive
|
||||
} deriving (Eq, Ord, Show, Generic, Typeable)
|
||||
@ -1,25 +0,0 @@
|
||||
module Utils.Tutorial
|
||||
( isTutorialTutor, isTutorialParticipant
|
||||
) where
|
||||
|
||||
import Import
|
||||
|
||||
import qualified Database.Esqueleto as E
|
||||
import qualified Database.Esqueleto.Utils as E
|
||||
|
||||
|
||||
isTutorialTutor :: Maybe UserId -> AuthTagActive -> E.SqlExpr (E.Value TutorialId) -> E.SqlExpr (E.Value Bool)
|
||||
isTutorialTutor muid AuthTagActive{..} tid
|
||||
| Just uid <- muid, authTagIsActive AuthTutor = E.exists . E.from $ \(tutor `E.InnerJoin` tutorial) -> do
|
||||
E.on $ tutor E.^. TutorTutorial E.==. tutorial E.^. TutorialId
|
||||
E.where_ $ tutorial E.^. TutorialId E.==. tid
|
||||
E.&&. tutor E.^. TutorUser E.==. E.val uid
|
||||
| otherwise = E.false
|
||||
|
||||
isTutorialParticipant :: Maybe UserId -> AuthTagActive -> E.SqlExpr (E.Value TutorialId) -> E.SqlExpr (E.Value Bool)
|
||||
isTutorialParticipant muid AuthTagActive{..} tid
|
||||
| Just uid <- muid, authTagIsActive AuthTutorialRegistered = E.exists . E.from $ \(tutorialParticipant `E.InnerJoin` tutorial) -> do
|
||||
E.on $ tutorialParticipant E.^. TutorialParticipantTutorial E.==. tutorial E.^. TutorialId
|
||||
E.where_ $ tutorial E.^. TutorialId E.==. tid
|
||||
E.&&. tutorialParticipant E.^. TutorialParticipantUser E.==. E.val uid
|
||||
| otherwise = E.false
|
||||
@ -243,16 +243,12 @@ $# $if NTop (Just 0) < NTop (courseCapacity course)
|
||||
_{MsgCourseEventRoom}
|
||||
<th .table__th uw-hide-column-header="note" :hiddenEventNotes:.course-event-note--hidden>
|
||||
_{MsgCourseEventNote}
|
||||
$# TODO: merge with actions column
|
||||
$if is _Just mbAuth
|
||||
<th .table__th uw-hide-column-header="schedule-actions">
|
||||
_{MsgScheduleOptActions}
|
||||
$if mayCreateEvents
|
||||
<th .table__th uw-hide-column-header="actions">
|
||||
_{MsgCourseEventActions}
|
||||
\ #{iconInvisible}
|
||||
<tbody>
|
||||
$forall (cID, CourseEvent{courseEventType, courseEventTime, courseEventRoom, courseEventNote}, showRoom, courseEventCurrentOpt, mEventScheduleOpt) <- events
|
||||
$forall (cID, CourseEvent{courseEventType, courseEventTime, courseEventRoom, courseEventNote}, showRoom) <- events
|
||||
<tr .table__row ##{"event-" <> toPathPiece cID}>
|
||||
<td .table__td>
|
||||
<div .table__td-content>
|
||||
@ -273,15 +269,6 @@ $# $if NTop (Just 0) < NTop (courseCapacity course)
|
||||
<td .table__td :hiddenEventNotes:.course-event-note--hidden>
|
||||
<div .table__td-content>
|
||||
#{courseEventNote}
|
||||
$# TODO: merge with actions column
|
||||
$if is _Just mbAuth
|
||||
<td .table__td>
|
||||
<div .table__td-content>
|
||||
<a .btn .btn-primary href=@{CEventR tid ssh csh cID (CEvScheduleOptSetR (not courseEventCurrentOpt))}>
|
||||
_{bool MsgScheduleOptIn MsgScheduleOptOut courseEventCurrentOpt}
|
||||
$if is _Just mEventScheduleOpt
|
||||
<a .btn .btn-primary href=@{CEventR tid ssh csh cID CEvScheduleOptDelR}>
|
||||
_{MsgScheduleOptDelete}
|
||||
$if mayCreateEvents
|
||||
<td .table__td>
|
||||
<ul .list--inline .list--iconless .list--comma-separated>
|
||||
@ -306,12 +293,3 @@ $# $if NTop (Just 0) < NTop (courseCapacity course)
|
||||
<dd .deflist__dd>
|
||||
^{tutorialTable}
|
||||
|
||||
$maybe (_, user) <- mbAuth
|
||||
<dt .deflist__dt>
|
||||
_{MsgScheduleOptActions}
|
||||
<dd .deflist__dd>
|
||||
<a .btn .btn-primary href=@{CourseR tid ssh csh (CScheduleOptSetR (not (courseScheduleOptToggleValue user)))}>
|
||||
_{bool MsgCourseScheduleOptIn MsgCourseScheduleOptOut (courseScheduleOptToggleValue user)}
|
||||
$if is _Just mCourseScheduleOpt
|
||||
<a .btn .btn-primary href=@{CourseR tid ssh csh CScheduleOptDelR}>
|
||||
_{MsgCourseScheduleOptDelete}
|
||||
|
||||
@ -224,7 +224,7 @@ $if not (null occurrences)
|
||||
$if not occurrenceAssignmentsVisible
|
||||
\ ^{isVisible False}
|
||||
$of _
|
||||
<th .table__th>
|
||||
<th .table__td>
|
||||
$if not occurrenceAssignmentsVisible
|
||||
^{isVisible False}
|
||||
$if showRegisteredCount
|
||||
@ -232,11 +232,8 @@ $if not (null occurrences)
|
||||
_{MsgExamRegisteredCount}
|
||||
\ ^{isVisible False}
|
||||
<th .table__th>_{MsgExamRoomDescription}
|
||||
$if is _Just mAuth
|
||||
<th .table__th>
|
||||
_{MsgSchedule}
|
||||
<tbody>
|
||||
$forall (occurrence, registered, rCount, showRoom, shouldBeDisplayedInSchedule, mEOScheduleOpt) <- occurrences
|
||||
$forall (occurrence, registered, rCount, showRoom) <- occurrences
|
||||
$with Entity _occId ExamOccurrence{examOccurrenceName, examOccurrenceRoom, examOccurrenceStart, examOccurrenceEnd, examOccurrenceDescription} <- occurrence
|
||||
$with registerWdgt <- registerWidget (Just occurrence)
|
||||
<tr .table__row :markUnregisteredOccurrences (Just occurrence) && not registered:.occurrence--not-registered>
|
||||
@ -271,14 +268,6 @@ $if not (null occurrences)
|
||||
<td .table__td>
|
||||
$maybe desc <- examOccurrenceDescription
|
||||
#{desc}
|
||||
$if is _Just mAuth
|
||||
<td .table__td>
|
||||
<div .table__td-content>
|
||||
<a .btn .btn-primary href=@{CExamR tid ssh csh examn (EScheduleOptSetR examOccurrenceName (not shouldBeDisplayedInSchedule))}>
|
||||
_{bool MsgScheduleOptIn MsgScheduleOptOut shouldBeDisplayedInSchedule}
|
||||
$if is _Just mEOScheduleOpt
|
||||
<a .btn .btn-primary href=@{CExamR tid ssh csh examn (EScheduleOptDelR examOccurrenceName)}>
|
||||
_{MsgScheduleOptDelete}
|
||||
<tfoot>
|
||||
<tr .table__row .table__row--sum>
|
||||
$if occurrenceNamesShown
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
$# TODO: Erklärungen (bzw. Teile davon) in FAQ wiederverwenden
|
||||
|
||||
<p>
|
||||
In Ihrer persönlichen Terminübersicht werden Ihnen Ihre Termine zu Uni2work-Kursen angezeigt.
|
||||
|
||||
<p>
|
||||
Sie können auf einzelne Termine in Ihrer Terminübersicht klicken, um zum jeweiligen Kurs, zum jeweiligen Tutorium oder zur jeweiligen Prüfung zu gelangen.
|
||||
|
||||
<p>
|
||||
Es gibt die Möglichkeit, einzelne Termine aus Ihrer Terminübersicht auszublenden. Wenn Sie einzelne Termine aus Ihrer persönlichen Terminübersicht ausblenden möchten, dann klicken Sie zunächst auf den jeweils auszublendenden Termin, und fügen dann über den Knopf "Aus Terminübersicht ausblenden" eine Ausnahme für diesen Termin hinzu. <br />
|
||||
Analog dazu können Sie ausgeblendete Termine auch in Ihrer Terminübersicht einblenden, indem Sie den Knopf "In Terminübersicht zeigen" betätigen; diesen finden Sie an der gleichen Stelle, an der sonst der Knopf "Aus Terminübersicht ausblenden" zu finden ist. Alternativ können Sie auch über Ihre Benutzereinstellungen Ihre Terminausnahmen einsehen und entfernen. <br />
|
||||
In Ihren Benutzereinstellungen können Sie festlegen, ob Termine von Kursen, zu denen Sie sich neu anmelden standardmäßig in Ihrer Terminübersicht angezeigt werden sollen.
|
||||
|
||||
<p>
|
||||
In Ihren Benutzereinstellungen haben Sie verschiedene Möglichkeiten, die Anzeige Ihrer Terminübersicht zu beeinflussen. <br />
|
||||
Beispielsweise können Sie dort in der Wochenübersicht darzustellende Wochentage festlegen (Samstag und Sonntag sind standrdmäßig ausgeblendet, wenn Sie an diesem Tag keinen Termin haben), oder auch Beginn und Ende (Uhrzeit) eines Tages in der Wochenübersicht sowie die Länge der darzustellenden Zeitslots (d.h. einer Zeilen) festlegen. Sie können dort auch festlegen, ob Termine von Kursen, zu denen Sie sich neu anmelden standardmäßig in Ihrem Stundenplan angezeigt werden sollen.
|
||||
Die relevanten Einstellungen hierzu finden Sie in Ihren Benutzereinstellungen (diese erreichen Sie über "Anpassen" rechts oben), und dort in der Sektion "Terminübersicht".
|
||||
@ -1,18 +0,0 @@
|
||||
$# TODO: Erklärungen (bzw. Teile davon) in FAQ wiederverwenden
|
||||
|
||||
<p>
|
||||
This is your personal schedule, in which your appointments for your Uni2work courses are displayed.
|
||||
|
||||
<p>
|
||||
You can click on a specific appointment to reach the respective course, tutorial or exam.
|
||||
|
||||
<p>
|
||||
You may hide specific appointments from your personal schedule. To hide a specific appointment, please click on the appointment in your schedule, and hit the button "Hide from schedule" there to opt-out of this appointment in your schedule. <br />
|
||||
Similarly, you may also show specific appointments in your schedule; in this case, hit the button "Show in schedule" (which will be displayed at the same place where you normally find the button "Hide from schedule"). Alternatively, you may also view and remove your appointment display opt-ins/opt-outs in your user settings. <br />
|
||||
In your user settings, you may specify whether appointments for courses should be displayed by default after you registered for the course.
|
||||
|
||||
<p>
|
||||
You may change the look and behaviour of your personal schedule in various ways in your user settings. <br />
|
||||
Beispielsweise können Sie dort in der Wochenübersicht darzustellende Wochentage festlegen (Samstag und Sonntag sind standrdmäßig ausgeblendet, wenn Sie an diesem Tag keinen Termin haben), oder auch Beginn und Ende (Uhrzeit) eines Tages in der Wochenübersicht sowie die Länge der darzustellenden Zeitslots (d.h. einer Zeilen) festlegen. Sie können dort auch festlegen, ob Termine von Kursen, zu denen Sie sich neu anmelden standardmäßig in Ihrem Stundenplan angezeigt werden sollen.
|
||||
For example, you may change the weekdays which should be displayed by default in your weekly schedule (Saturdays and Sundays are hidden by default if you do not have any appointments on this day), or you may change the begin and end (time) of each day in your weekly schedule such as the length of each time slot (i.e. the time length of each row). You may also specify whether appointments for new courses should be displayed in your schedule by default after you register for the course. <br />
|
||||
All relevant settings can be found in your user settings (which you can view via "Settings" in the upper right corner), and there under the section "Schedule".
|
||||
@ -1,6 +0,0 @@
|
||||
$newline never
|
||||
<section>
|
||||
<h2>
|
||||
<a href=@{ScheduleR}>
|
||||
_{MsgSchedule}
|
||||
^{schedule}
|
||||
@ -1,10 +0,0 @@
|
||||
$newline never
|
||||
|
||||
<section>
|
||||
^{schedule}
|
||||
|
||||
<form enctype=#{optionsEnctype} .schedule-options>
|
||||
^{optionsWidget}
|
||||
|
||||
<section .explanation>
|
||||
^{scheduleExplanation}
|
||||
@ -1,10 +0,0 @@
|
||||
$newline never
|
||||
|
||||
#{csrf}
|
||||
|
||||
$if length viewWidgets > 1
|
||||
$forall vWgt <- viewWidgets
|
||||
^{fvWidget vWgt}
|
||||
|
||||
$forall oWgt <- offsetWidgets
|
||||
^{fvWidget oWgt}
|
||||
@ -1,77 +0,0 @@
|
||||
$newline never
|
||||
<div .schedule uw-hide-columns="schedule-week">
|
||||
<table .table .table--striped .table--hover .schedule>
|
||||
<thead>
|
||||
<tr .table__row .table__row--head>
|
||||
<th .table__th uw-hide-column-header="time">
|
||||
_{MsgScheduleTableHeadTime}
|
||||
$forall day <- week
|
||||
$if is _Just (Map.lookup day events)
|
||||
<th .table__th :isToday day:.schedule-current uw-hide-column-header=#{dayTableHeadIdent day}>
|
||||
^{formatTimeW SelFormatDate day}
|
||||
$if elem day holidays
|
||||
\ (_{MsgScheduleWeekHoliday})
|
||||
<tbody>
|
||||
$forall slot <- allTimeSlots
|
||||
$if Set.member slot timeSlotsDefaultDisplay || not (timeSlotIsEmpty slot)
|
||||
<tr .table__row>
|
||||
<th .table__th uw-hide-columns--no-hide :any isToday week && isCurrentSlot slot:.schedule-current>
|
||||
^{formatTimeSlotW slot}
|
||||
$forall day <- week
|
||||
$maybe dayEvents <- Map.lookup day events
|
||||
$maybe slotEvents <- Map.lookup slot dayEvents
|
||||
<td .table__td>
|
||||
<div .table__td-content>
|
||||
$forall (scheduleEntry, slotAssociation) <- slotEvents
|
||||
<a href=@{scheduleEntryToHref scheduleEntry} .schedule--entry-link>
|
||||
<div .schedule--entry .schedule--entry__#{toPathPiece slotAssociation} :slotAssocIsCont slotAssociation:.schedule--entry__continuation :isCurrentScheduleEntry day slot scheduleEntry:.schedule-current>
|
||||
$case scheduleEntry
|
||||
$of ScheduleCourseEvent{sceCourse=Entity _ Course{courseName},sceType,sceRoom,sceShowRoom,sceOccurrence}
|
||||
#{CI.original courseName}: #{CI.original sceType} #
|
||||
$if slotAssocIsCont slotAssociation
|
||||
(_{MsgScheduleWeekSlotIsCont})
|
||||
<br>
|
||||
$if sceShowRoom
|
||||
$maybe room <- sceRoom
|
||||
_{MsgScheduleRoom}: ^{roomReferenceWidget room}
|
||||
<br>
|
||||
^{formatEitherOccurrenceW sceOccurrence}
|
||||
$of ScheduleTutorial{stCourse=Entity _ Course{courseName},stName,stType,stRoom,stShowRoom,stOccurrence}
|
||||
#{CI.original courseName}: #{stName} #
|
||||
(
|
||||
#{CI.original stType}
|
||||
$if slotAssocIsCont slotAssociation
|
||||
, _{MsgScheduleWeekSlotIsCont}
|
||||
)
|
||||
<br>
|
||||
$if stShowRoom
|
||||
$maybe room <- stRoom
|
||||
_{MsgScheduleRoom}: ^{roomReferenceWidget room}
|
||||
<br>
|
||||
^{formatEitherOccurrenceW stOccurrence}
|
||||
$of ScheduleExamOccurrence{seoCourse=Entity _ Course{courseName},seoExamName,seoRooms,seoStart,seoEnd}
|
||||
#{CI.original courseName}: #{seoExamName} #
|
||||
$if slotAssocIsCont slotAssociation
|
||||
(_{MsgScheduleWeekSlotIsCont})
|
||||
<br>
|
||||
$case Set.toList seoRooms
|
||||
$of []
|
||||
$of [(mRoom, showRoom)]
|
||||
$if showRoom
|
||||
$maybe room <- mRoom
|
||||
_{MsgScheduleRoom}: ^{roomReferenceWidget room}
|
||||
<br>
|
||||
$of more
|
||||
_{MsgScheduleRooms}: #
|
||||
$forall (idx,(mRoom,showRoom)) <- indexedList more
|
||||
$if showRoom
|
||||
$maybe room <- mRoom
|
||||
^{roomReferenceWidget room}
|
||||
$if idx < pred (length more)
|
||||
; #
|
||||
<br>
|
||||
_{MsgScheduleOccur}: #
|
||||
$if Just (utctDay seoStart) == fmap utctDay seoEnd
|
||||
^{formatTimeRangeW SelFormatTime seoStart seoEnd}
|
||||
$else
|
||||
^{formatTimeRangeW SelFormatDateTime seoStart seoEnd}
|
||||
@ -0,0 +1,7 @@
|
||||
$newline never
|
||||
|
||||
$# SPDX-FileCopyrightText: 2022 Sarah Vaupel <vaupel.sarah@campus.lmu.de>
|
||||
$#
|
||||
$# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
_{MsgExceptionKindNoOccur}: #{exceptTime'}
|
||||
7
templates/widgets/occurrence/cell/except-occurr.hamlet
Normal file
7
templates/widgets/occurrence/cell/except-occurr.hamlet
Normal file
@ -0,0 +1,7 @@
|
||||
$newline never
|
||||
|
||||
$# SPDX-FileCopyrightText: 2022 Sarah Vaupel <vaupel.sarah@campus.lmu.de>
|
||||
$#
|
||||
$# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
_{MsgExceptionKindOccur}: #{exceptStart'}–#{exceptEnd'}
|
||||
@ -1,4 +1,4 @@
|
||||
-- SPDX-FileCopyrightText: 2022-2024 Sarah Vaupel <sarah.vaupel@uniworx.de>, Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <sarah.vaupel@ifi.lmu.de>,Steffen Jost <jost@cip.ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>,Steffen Jost <s.jost@fraport.de>
|
||||
-- SPDX-FileCopyrightText: 2022-24 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <sarah.vaupel@ifi.lmu.de>,Steffen Jost <jost@cip.ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>,Steffen Jost <s.jost@fraport.de>
|
||||
--
|
||||
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@ -98,7 +98,6 @@ fillDb = do
|
||||
, userDateTimeFormat = userDefaultDateTimeFormat
|
||||
, userDateFormat = userDefaultDateFormat
|
||||
, userTimeFormat = userDefaultTimeFormat
|
||||
, userWeekStart = userDefaultWeekStart
|
||||
, userDownloadFiles = userDefaultDownloadFiles
|
||||
, userWarningDays = userDefaultWarningDays
|
||||
, userLanguages = Just $ Languages ["en"]
|
||||
@ -110,12 +109,6 @@ fillDb = do
|
||||
, userSex = Just SexMale
|
||||
, userBirthday = Nothing
|
||||
, userShowSex = userDefaultShowSex
|
||||
, userScheduleView = userDefaultScheduleView
|
||||
, userScheduleWeekDays = userDefaultScheduleWeekDays
|
||||
, userScheduleWeekTimeFrom = userDefaultScheduleWeekTimeFrom
|
||||
, userScheduleWeekTimeTo = userDefaultScheduleWeekTimeTo
|
||||
, userScheduleWeekTimeslotLength = userDefaultScheduleWeekTimeslotLength
|
||||
, userScheduleOccurrenceDisplayDefault = userDefaultScheduleOccurrenceDisplayDefault
|
||||
, userTelephone = Nothing
|
||||
, userMobile = Nothing
|
||||
, userCompanyPersonalNumber = Just "00000"
|
||||
@ -145,7 +138,6 @@ fillDb = do
|
||||
, userDateTimeFormat = userDefaultDateTimeFormat
|
||||
, userDateFormat = userDefaultDateFormat
|
||||
, userTimeFormat = userDefaultTimeFormat
|
||||
, userWeekStart = userDefaultWeekStart
|
||||
, userDownloadFiles = userDefaultDownloadFiles
|
||||
, userWarningDays = userDefaultWarningDays
|
||||
, userLanguages = Nothing
|
||||
@ -156,12 +148,6 @@ fillDb = do
|
||||
, userCsvOptions = def { csvFormat = csvPreset # CsvPresetExcel }
|
||||
, userSex = Just SexMale
|
||||
, userShowSex = userDefaultShowSex
|
||||
, userScheduleView = userDefaultScheduleView
|
||||
, userScheduleWeekDays = userDefaultScheduleWeekDays
|
||||
, userScheduleWeekTimeFrom = userDefaultScheduleWeekTimeFrom
|
||||
, userScheduleWeekTimeTo = userDefaultScheduleWeekTimeTo
|
||||
, userScheduleWeekTimeslotLength = userDefaultScheduleWeekTimeslotLength
|
||||
, userScheduleOccurrenceDisplayDefault = userDefaultScheduleOccurrenceDisplayDefault
|
||||
, userBirthday = Nothing
|
||||
, userMobile = Nothing
|
||||
, userTelephone = Nothing
|
||||
@ -198,7 +184,6 @@ fillDb = do
|
||||
, userDateTimeFormat = userDefaultDateTimeFormat
|
||||
, userDateFormat = userDefaultDateFormat
|
||||
, userTimeFormat = userDefaultTimeFormat
|
||||
, userWeekStart = userDefaultWeekStart
|
||||
, userDownloadFiles = userDefaultDownloadFiles
|
||||
, userWarningDays = userDefaultWarningDays
|
||||
, userLanguages = Nothing
|
||||
@ -210,12 +195,6 @@ fillDb = do
|
||||
, userBirthday = Just $ n_day $ 35 * (-365)
|
||||
, userCsvOptions = def
|
||||
, userShowSex = userDefaultShowSex
|
||||
, userScheduleView = userDefaultScheduleView
|
||||
, userScheduleWeekDays = userDefaultScheduleWeekDays
|
||||
, userScheduleWeekTimeFrom = userDefaultScheduleWeekTimeFrom
|
||||
, userScheduleWeekTimeTo = userDefaultScheduleWeekTimeTo
|
||||
, userScheduleWeekTimeslotLength = userDefaultScheduleWeekTimeslotLength
|
||||
, userScheduleOccurrenceDisplayDefault = userDefaultScheduleOccurrenceDisplayDefault
|
||||
, userTelephone = Just "+49 69 690-71706"
|
||||
, userMobile = Just "0173 69 99 646"
|
||||
, userCompanyPersonalNumber = Just "57138"
|
||||
@ -245,7 +224,6 @@ fillDb = do
|
||||
, userMaxFavouriteTerms = userDefaultMaxFavouriteTerms
|
||||
, userDateFormat = userDefaultDateFormat
|
||||
, userTimeFormat = userDefaultTimeFormat
|
||||
, userWeekStart = userDefaultWeekStart
|
||||
, userDownloadFiles = userDefaultDownloadFiles
|
||||
, userWarningDays = userDefaultWarningDays
|
||||
, userLanguages = Just $ Languages ["de"]
|
||||
@ -257,12 +235,6 @@ fillDb = do
|
||||
, userSex = Just SexMale
|
||||
, userBirthday = Just $ n_day $ 27 * (-365)
|
||||
, userShowSex = userDefaultShowSex
|
||||
, userScheduleView = userDefaultScheduleView
|
||||
, userScheduleWeekDays = userDefaultScheduleWeekDays
|
||||
, userScheduleWeekTimeFrom = userDefaultScheduleWeekTimeFrom
|
||||
, userScheduleWeekTimeTo = userDefaultScheduleWeekTimeTo
|
||||
, userScheduleWeekTimeslotLength = userDefaultScheduleWeekTimeslotLength
|
||||
, userScheduleOccurrenceDisplayDefault = userDefaultScheduleOccurrenceDisplayDefault
|
||||
, userTelephone = Nothing
|
||||
, userMobile = Nothing
|
||||
, userCompanyPersonalNumber = Nothing
|
||||
@ -292,7 +264,6 @@ fillDb = do
|
||||
, userDateTimeFormat = userDefaultDateTimeFormat
|
||||
, userDateFormat = userDefaultDateFormat
|
||||
, userTimeFormat = userDefaultTimeFormat
|
||||
, userWeekStart = userDefaultWeekStart
|
||||
, userDownloadFiles = userDefaultDownloadFiles
|
||||
, userWarningDays = userDefaultWarningDays
|
||||
, userLanguages = Just $ Languages ["sn"]
|
||||
@ -304,12 +275,6 @@ fillDb = do
|
||||
, userSex = Just SexNotApplicable
|
||||
, userBirthday = Just $ n_day 3
|
||||
, userShowSex = userDefaultShowSex
|
||||
, userScheduleView = userDefaultScheduleView
|
||||
, userScheduleWeekDays = userDefaultScheduleWeekDays
|
||||
, userScheduleWeekTimeFrom = userDefaultScheduleWeekTimeFrom
|
||||
, userScheduleWeekTimeTo = userDefaultScheduleWeekTimeTo
|
||||
, userScheduleWeekTimeslotLength = userDefaultScheduleWeekTimeslotLength
|
||||
, userScheduleOccurrenceDisplayDefault = userDefaultScheduleOccurrenceDisplayDefault
|
||||
, userTelephone = Nothing
|
||||
, userMobile = Nothing
|
||||
, userCompanyPersonalNumber = Just "12345"
|
||||
@ -326,7 +291,7 @@ fillDb = do
|
||||
, userAuthentication = AuthLDAP
|
||||
, userLastAuthentication = Nothing
|
||||
, userTokensIssuedAfter = Nothing
|
||||
, userMatrikelnummer = Just "11323801"
|
||||
, userMatrikelnummer = Nothing
|
||||
, userEmail = "vaupel.sarah@campus.lmu.de"
|
||||
, userDisplayEmail = "vaupel.sarah@campus.lmu.de"
|
||||
, userDisplayName = "Sarah Vaupel"
|
||||
@ -335,11 +300,10 @@ fillDb = do
|
||||
, userTitle = Nothing
|
||||
, userMaxFavourites = 14
|
||||
, userMaxFavouriteTerms = 4
|
||||
, userTheme = ThemeNeutralBlue
|
||||
, userTheme = ThemeMossGreen
|
||||
, userDateTimeFormat = userDefaultDateTimeFormat
|
||||
, userDateFormat = userDefaultDateFormat
|
||||
, userTimeFormat = userDefaultTimeFormat
|
||||
, userWeekStart = userDefaultWeekStart
|
||||
, userDownloadFiles = userDefaultDownloadFiles
|
||||
, userWarningDays = userDefaultWarningDays
|
||||
, userLanguages = Nothing
|
||||
@ -351,12 +315,6 @@ fillDb = do
|
||||
, userSex = Just SexFemale
|
||||
, userBirthday = Nothing
|
||||
, userShowSex = userDefaultShowSex
|
||||
, userScheduleView = userDefaultScheduleView
|
||||
, userScheduleWeekDays = userDefaultScheduleWeekDays
|
||||
, userScheduleWeekTimeFrom = userDefaultScheduleWeekTimeFrom
|
||||
, userScheduleWeekTimeTo = userDefaultScheduleWeekTimeTo
|
||||
, userScheduleWeekTimeslotLength = userDefaultScheduleWeekTimeslotLength
|
||||
, userScheduleOccurrenceDisplayDefault = userDefaultScheduleOccurrenceDisplayDefault
|
||||
, userTelephone = Nothing
|
||||
, userMobile = Nothing
|
||||
, userCompanyPersonalNumber = Nothing
|
||||
@ -386,7 +344,6 @@ fillDb = do
|
||||
, userDateTimeFormat = userDefaultDateTimeFormat
|
||||
, userDateFormat = userDefaultDateFormat
|
||||
, userTimeFormat = userDefaultTimeFormat
|
||||
, userWeekStart = userDefaultWeekStart
|
||||
, userDownloadFiles = userDefaultDownloadFiles
|
||||
, userWarningDays = userDefaultWarningDays
|
||||
, userLanguages = Nothing
|
||||
@ -398,12 +355,6 @@ fillDb = do
|
||||
, userSex = Just SexMale
|
||||
, userBirthday = Nothing
|
||||
, userShowSex = userDefaultShowSex
|
||||
, userScheduleView = userDefaultScheduleView
|
||||
, userScheduleWeekDays = userDefaultScheduleWeekDays
|
||||
, userScheduleWeekTimeFrom = userDefaultScheduleWeekTimeFrom
|
||||
, userScheduleWeekTimeTo = userDefaultScheduleWeekTimeTo
|
||||
, userScheduleWeekTimeslotLength = userDefaultScheduleWeekTimeslotLength
|
||||
, userScheduleOccurrenceDisplayDefault = userDefaultScheduleOccurrenceDisplayDefault
|
||||
, userTelephone = Nothing
|
||||
, userMobile = Nothing
|
||||
, userCompanyPersonalNumber = Nothing
|
||||
@ -593,7 +544,6 @@ fillDb = do
|
||||
, userDateTimeFormat = userDefaultDateTimeFormat
|
||||
, userDateFormat = userDefaultDateFormat
|
||||
, userTimeFormat = userDefaultTimeFormat
|
||||
, userWeekStart = userDefaultWeekStart
|
||||
, userDownloadFiles = userDefaultDownloadFiles
|
||||
, userWarningDays = userDefaultWarningDays
|
||||
, userLanguages = Nothing
|
||||
@ -605,12 +555,6 @@ fillDb = do
|
||||
, userSex = Nothing
|
||||
, userBirthday = Nothing
|
||||
, userShowSex = userDefaultShowSex
|
||||
, userScheduleView = userDefaultScheduleView
|
||||
, userScheduleWeekDays = userDefaultScheduleWeekDays
|
||||
, userScheduleWeekTimeFrom = userDefaultScheduleWeekTimeFrom
|
||||
, userScheduleWeekTimeTo = userDefaultScheduleWeekTimeTo
|
||||
, userScheduleWeekTimeslotLength = userDefaultScheduleWeekTimeslotLength
|
||||
, userScheduleOccurrenceDisplayDefault = userDefaultScheduleOccurrenceDisplayDefault
|
||||
, userTelephone = Nothing
|
||||
, userMobile = Nothing
|
||||
, userCompanyPersonalNumber = bool Nothing (Just "E123" ) (even $ length firstName)
|
||||
|
||||
Reference in New Issue
Block a user