Compare commits
160 Commits
master
...
stundenpla
| Author | SHA1 | Date | |
|---|---|---|---|
| 0e4ffce9e8 | |||
| c946a0e2c1 | |||
|
|
8ad6d5164c | ||
|
|
8d06a035b1 | ||
|
|
e4ba4414c6 | ||
|
|
0c8de277d5 | ||
|
|
4e6ffa7b85 | ||
|
|
9f954061e3 | ||
|
|
4a726f09fb | ||
|
|
a4a26afa7a | ||
|
|
bbd5b73142 | ||
|
|
f178737f78 | ||
|
|
c1b94dbb53 | ||
|
|
6b8a140aca | ||
|
|
f46f23785d | ||
|
|
99475ed253 | ||
|
|
7733bd6280 | ||
|
|
5bc25d1d3f | ||
|
|
4bc1a8eac0 | ||
|
|
dba0891000 | ||
|
|
ff9916fde6 | ||
|
|
22f43a9631 | ||
|
|
265d5f3ddd | ||
|
|
3919152ede | ||
|
|
601cb3179f | ||
|
|
ec04fe161e | ||
|
|
45a5766210 | ||
|
|
3589831541 | ||
|
|
62c8296c6a | ||
|
|
4282af893d | ||
|
|
b0023dfa67 | ||
|
|
1d34cae4e2 | ||
|
|
42c133d3ed | ||
|
|
d8a921f335 | ||
|
|
cd450848a4 | ||
|
|
374cb6250d | ||
|
|
4199cc624b | ||
|
|
2c9d5e0a22 | ||
|
|
c6a84b314c | ||
|
|
bab72a5e2e | ||
|
|
2ceced4b64 | ||
|
|
716f31d925 | ||
|
|
ceb4df3c63 | ||
|
|
c41e3b6bb3 | ||
|
|
2bbe67bf90 | ||
|
|
a6308544c8 | ||
|
|
551f64a842 | ||
|
|
39a0eedf5a | ||
|
|
c6cd121ad4 | ||
|
|
cb3f74a2a9 | ||
|
|
7f48a2d693 | ||
|
|
43e5a67164 | ||
|
|
fd276879ad | ||
|
|
0ecc3c689f | ||
|
|
5f9aad8aa9 | ||
|
|
868a4afcc6 | ||
|
|
d0fe60b951 | ||
|
|
c984947598 | ||
|
|
d8a61ed307 | ||
|
|
2c62a988df | ||
|
|
c7e6c3c086 | ||
|
|
51984cde87 | ||
|
|
61545cade0 | ||
|
|
6f4891bb90 | ||
|
|
e2b2b8e7e1 | ||
|
|
766397d114 | ||
|
|
5e0737d1b1 | ||
|
|
e21536f85d | ||
|
|
8b49bf866e | ||
|
|
ef8c572860 | ||
|
|
4a1002c2ce | ||
|
|
52d027259f | ||
|
|
3b90b9caa9 | ||
|
|
4f13bd422c | ||
|
|
6aaa5cc477 | ||
|
|
dc4bbbd97b | ||
|
|
145564cf77 | ||
|
|
0c9671b3d9 | ||
|
|
4151f62fa5 | ||
|
|
f5713fdb65 | ||
|
|
b757acb522 | ||
|
|
304a60560d | ||
|
|
913320a2e9 | ||
|
|
0d8a613ad6 | ||
|
|
6c0a0a2f53 | ||
|
|
df3262b8a0 | ||
|
|
57c1cc768c | ||
|
|
db7238da5e | ||
|
|
ae0e3f797f | ||
|
|
fe4507cdad | ||
|
|
3489ef7926 | ||
|
|
fc238ab474 | ||
|
|
04341d2e49 | ||
|
|
cb61482b83 | ||
|
|
2c021d0ae2 | ||
|
|
ae753e5a4f | ||
|
|
be442c6058 | ||
|
|
c996049b3f | ||
|
|
d03a7149a4 | ||
|
|
be700882e1 | ||
|
|
acb663c480 | ||
|
|
e8adafd123 | ||
|
|
c866acf600 | ||
|
|
eeb365ab5c | ||
|
|
a9b791c554 | ||
|
|
7241afd9d2 | ||
|
|
d7366652bf | ||
|
|
4316606743 | ||
|
|
1de2c7f9d3 | ||
|
|
3cf0188d2a | ||
|
|
5da9a1499c | ||
|
|
11c5aa0f10 | ||
|
|
44de231f01 | ||
|
|
9c36c2fb85 | ||
|
|
2d921ba20b | ||
|
|
9fb4aa1429 | ||
|
|
2283a881be | ||
|
|
e43009ba0c | ||
|
|
d3afd526ed | ||
|
|
02767b4c5b | ||
|
|
6ed4eab44f | ||
|
|
dddb2746e5 | ||
|
|
66352522da | ||
|
|
390a53b982 | ||
|
|
a651e3d62d | ||
|
|
2428e5ec72 | ||
|
|
3be331f043 | ||
|
|
67302a5dd1 | ||
|
|
798a0811b7 | ||
|
|
78de1d56ae | ||
|
|
a025e57817 | ||
|
|
2baf76f138 | ||
|
|
d8227dcf8d | ||
|
|
ed40b89bfe | ||
|
|
ed5101c26c | ||
|
|
280a19865c | ||
|
|
7856aba24d | ||
|
|
113f21fc29 | ||
|
|
5bd0e7d050 | ||
|
|
d19be72f58 | ||
|
|
7c4dc0d6d6 | ||
|
|
c0fb5adec0 | ||
|
|
428b8cf739 | ||
|
|
db49943baf | ||
|
|
d82c6b073f | ||
|
|
2a82ac62e4 | ||
|
|
0aae46a0b9 | ||
|
|
75bf13ae16 | ||
|
|
9b869b0bb5 | ||
|
|
693b36e789 | ||
|
|
3416e63f6f | ||
|
|
3254d34dc4 | ||
|
|
6245079465 | ||
|
|
4007122265 | ||
|
|
2d38172363 | ||
|
|
6ac1dc57d0 | ||
|
|
38fc5fa986 | ||
|
|
2ea234259b | ||
|
|
9b78a5be12 | ||
|
|
6b585f8dae |
@ -282,9 +282,16 @@ user-defaults:
|
|||||||
date-time-format: "%d.%m.%Y %R"
|
date-time-format: "%d.%m.%Y %R"
|
||||||
date-format: "%d.%m.%y"
|
date-format: "%d.%m.%y"
|
||||||
time-format: "%R"
|
time-format: "%R"
|
||||||
|
week-start: Monday
|
||||||
download-files: false
|
download-files: false
|
||||||
warning-days: 1209600
|
warning-days: 1209600
|
||||||
show-sex: false
|
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-synced: true
|
||||||
exam-office-get-labels: true
|
exam-office-get-labels: true
|
||||||
prefers-postal: true
|
prefers-postal: true
|
||||||
|
|||||||
@ -263,6 +263,13 @@ button:not(.btn-link),
|
|||||||
&.btn-danger
|
&.btn-danger
|
||||||
background-color: var(--color-error-dark)
|
background-color: var(--color-error-dark)
|
||||||
|
|
||||||
|
.fa,.fas
|
||||||
|
color: white
|
||||||
|
.tooltip__handle
|
||||||
|
cursor: pointer
|
||||||
|
.tooltip__content
|
||||||
|
color: var(--color-font)
|
||||||
|
|
||||||
.buttongroup
|
.buttongroup
|
||||||
display: grid
|
display: grid
|
||||||
grid: min-content / auto-flow max-content
|
grid: min-content / auto-flow max-content
|
||||||
@ -280,6 +287,8 @@ button[disabled]:not(.btn-link),
|
|||||||
opacity: 0.3
|
opacity: 0.3
|
||||||
background-color: var(--color-grey)
|
background-color: var(--color-grey)
|
||||||
cursor: default
|
cursor: default
|
||||||
|
.tooltip__handle
|
||||||
|
cursor: default
|
||||||
|
|
||||||
input[type="submit"]:not([disabled]):not(.btn-link):hover,
|
input[type="submit"]:not([disabled]):not(.btn-link):hover,
|
||||||
input[type="button"]:not([disabled]):not(.btn-link):hover,
|
input[type="button"]:not([disabled]):not(.btn-link):hover,
|
||||||
@ -1694,6 +1703,63 @@ video
|
|||||||
object-fit: contain
|
object-fit: contain
|
||||||
flex-grow: 1
|
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
|
.hr
|
||||||
height: 1px
|
height: 1px
|
||||||
width: 90%
|
width: 90%
|
||||||
@ -1760,4 +1826,4 @@ video
|
|||||||
color: var(--color-lightwhite)
|
color: var(--color-lightwhite)
|
||||||
&.nonactive
|
&.nonactive
|
||||||
background-color: var(--color-nonactive)
|
background-color: var(--color-nonactive)
|
||||||
color: var(--color-nonactive-dark)
|
color: var(--color-nonactive-dark)
|
||||||
@ -2,8 +2,6 @@
|
|||||||
#
|
#
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
#messages or constructors that are used all over the code
|
|
||||||
|
|
||||||
Logo !ident-ok: FRADrive
|
Logo !ident-ok: FRADrive
|
||||||
EmailInvitationWarning: Diese Adresse konnte keinem FRADrive-Benutzer/-Benutzerin zugeordnet werden. Es wird eine Einladung per E-Mail versandt.
|
EmailInvitationWarning: Diese Adresse konnte keinem FRADrive-Benutzer/-Benutzerin zugeordnet werden. Es wird eine Einladung per E-Mail versandt.
|
||||||
BoolIrrelevant !ident-ok: —
|
BoolIrrelevant !ident-ok: —
|
||||||
@ -31,4 +29,4 @@ PaginationPage: Angzeigte Seite
|
|||||||
PaginationError: Paginierung Parameter dürfen nicht negativ sein
|
PaginationError: Paginierung Parameter dürfen nicht negativ sein
|
||||||
|
|
||||||
NullDeletes: Zum Löschen NULL eingeben.
|
NullDeletes: Zum Löschen NULL eingeben.
|
||||||
SortPriority: Sortierungspriorität
|
SortPriority: Sortierungsprioritätz
|
||||||
@ -2,8 +2,6 @@
|
|||||||
#
|
#
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
#messages or constructors that are used all over the Code
|
|
||||||
|
|
||||||
Logo: FRADrive
|
Logo: FRADrive
|
||||||
EmailInvitationWarning: This address could not be matched to any FRADrive user. An invitation will be sent via email.
|
EmailInvitationWarning: This address could not be matched to any FRADrive user. An invitation will be sent via email.
|
||||||
BoolIrrelevant: —
|
BoolIrrelevant: —
|
||||||
|
|||||||
@ -9,7 +9,7 @@ DegreeCourse json -- for which degree programmes this course is appropriate fo
|
|||||||
UniqueDegreeCourse course degree terms
|
UniqueDegreeCourse course degree terms
|
||||||
deriving Generic
|
deriving Generic
|
||||||
Course -- Information about a single course; contained info is always visible to all users
|
Course -- Information about a single course; contained info is always visible to all users
|
||||||
name (CI Text)
|
name CourseName
|
||||||
description StoredMarkup Maybe -- user-defined large Html, ought to contain module description
|
description StoredMarkup Maybe -- user-defined large Html, ought to contain module description
|
||||||
linkExternal URI Maybe -- arbitrary user-defined url for external course page
|
linkExternal URI Maybe -- arbitrary user-defined url for external course page
|
||||||
shorthand (CI Text) -- practical shorthand of course name, used for identification
|
shorthand (CI Text) -- practical shorthand of course name, used for identification
|
||||||
@ -27,8 +27,17 @@ Course -- Information about a single course; contained info is always visible
|
|||||||
TermSchoolCourseShort term school shorthand -- shorthand must be unique within school and semester
|
TermSchoolCourseShort term school shorthand -- shorthand must be unique within school and semester
|
||||||
TermSchoolCourseName term school name -- name must be unique within school and semester
|
TermSchoolCourseName term school name -- name must be unique within school and semester
|
||||||
deriving Generic
|
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
|
CourseEvent
|
||||||
type (CI Text)
|
type CourseEventType
|
||||||
|
course CourseId
|
||||||
course CourseId OnDeleteCascade OnUpdateCascade
|
course CourseId OnDeleteCascade OnUpdateCascade
|
||||||
room RoomReference Maybe
|
room RoomReference Maybe
|
||||||
roomHidden Bool default=false
|
roomHidden Bool default=false
|
||||||
@ -36,6 +45,12 @@ CourseEvent
|
|||||||
note StoredMarkup Maybe
|
note StoredMarkup Maybe
|
||||||
lastChanged UTCTime default=now()
|
lastChanged UTCTime default=now()
|
||||||
deriving Generic
|
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
|
CourseAppInstructionFile
|
||||||
course CourseId OnDeleteCascade OnUpdateCascade
|
course CourseId OnDeleteCascade OnUpdateCascade
|
||||||
|
|||||||
@ -47,6 +47,12 @@ ExamOccurrence
|
|||||||
description StoredMarkup Maybe
|
description StoredMarkup Maybe
|
||||||
UniqueExamOccurrence exam name
|
UniqueExamOccurrence exam name
|
||||||
deriving Generic
|
deriving Generic
|
||||||
|
ExamOccurrenceScheduleOpt
|
||||||
|
examOccurrence ExamOccurrenceId
|
||||||
|
user UserId
|
||||||
|
opt Bool
|
||||||
|
UniqueExamOccurrenceScheduleOpt examOccurrence user
|
||||||
|
deriving Generic
|
||||||
ExamRegistration
|
ExamRegistration
|
||||||
exam ExamId
|
exam ExamId
|
||||||
user UserId
|
user UserId
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <vaupel.sarah@campus.lmu.de>
|
-- 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-License-Identifier: AGPL-3.0-or-later
|
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
Tutorial json
|
Tutorial json
|
||||||
name TutorialName
|
name TutorialName
|
||||||
course CourseId OnDeleteCascade OnUpdateCascade
|
course CourseId OnDeleteCascade OnUpdateCascade
|
||||||
type (CI Text) -- "Tutorium", "Zentralübung", ...
|
type TutorialType -- "Tutorium", "Zentralübung", ...
|
||||||
capacity Int Maybe -- limit for enrolment in this tutorial
|
capacity Int Maybe -- limit for enrolment in this tutorial
|
||||||
room RoomReference Maybe
|
room RoomReference Maybe
|
||||||
roomHidden Bool default=false
|
roomHidden Bool default=false
|
||||||
@ -29,4 +29,10 @@ TutorialParticipant
|
|||||||
user UserId
|
user UserId
|
||||||
UniqueTutorialParticipant tutorial user
|
UniqueTutorialParticipant tutorial user
|
||||||
deriving Eq Ord Show
|
deriving Eq Ord Show
|
||||||
deriving Generic
|
deriving Generic
|
||||||
|
TutorialScheduleOpt
|
||||||
|
tutorial TutorialId
|
||||||
|
user UserId
|
||||||
|
opt Bool
|
||||||
|
UniqueTutorialScheduleOpt tutorial user
|
||||||
|
deriving Generic
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
-- 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-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-License-Identifier: AGPL-3.0-or-later
|
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
@ -32,6 +32,7 @@ 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
|
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
|
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
|
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)
|
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
|
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
|
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
|
||||||
@ -40,6 +41,12 @@ User json -- Each Uni2work user has a corresponding row in this table; create
|
|||||||
sex Sex Maybe -- currently ignored
|
sex Sex Maybe -- currently ignored
|
||||||
birthday Day Maybe -- for better identification
|
birthday Day Maybe -- for better identification
|
||||||
showSex Bool default=false
|
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
|
telephone Text Maybe
|
||||||
mobile Text Maybe
|
mobile Text Maybe
|
||||||
companyPersonalNumber Text Maybe -- Company will become a new table, but if company=fraport, some information is received via LDAP
|
companyPersonalNumber Text Maybe -- Company will become a new table, but if company=fraport, some information is received via LDAP
|
||||||
|
|||||||
16
routes
16
routes
@ -170,7 +170,10 @@
|
|||||||
/course/#TermId/#SchoolId/#CourseShorthand CourseR !lecturer:
|
/course/#TermId/#SchoolId/#CourseShorthand CourseR !lecturer:
|
||||||
/ CShowR GET !tutor !corrector !exam-corrector !course-registered !course-time !evaluation !exam-office
|
/ CShowR GET !tutor !corrector !exam-corrector !course-registered !course-time !evaluation !exam-office
|
||||||
/favourite CFavouriteR GET POST !free
|
/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 CRegisterR GET POST !timeANDcapacityAND¬course-registeredANDcourse-time !timeAND¬exam-resultANDcourse-registered !lecturer
|
||||||
|
/register-template CRegisterTemplateR GET !course-time
|
||||||
/edit CEditR GET POST
|
/edit CEditR GET POST
|
||||||
/lecturer-invite CLecInviteR GET POST
|
/lecturer-invite CLecInviteR GET POST
|
||||||
/delete CDeleteR GET POST !lecturerANDempty
|
/delete CDeleteR GET POST !lecturerANDempty
|
||||||
@ -230,6 +233,8 @@
|
|||||||
/register TRegisterR POST !timeANDcapacityANDcourse-registeredANDregister-group !timeANDtutorial-registered
|
/register TRegisterR POST !timeANDcapacityANDcourse-registeredANDregister-group !timeANDtutorial-registered
|
||||||
/communication TCommR GET POST !tutor
|
/communication TCommR GET POST !tutor
|
||||||
/tutor-invite TInviteR GET POST !tutorANDtutor-control
|
/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 CExamListR GET !tutor !corrector !exam-corrector !course-registered !course-time !exam-office
|
||||||
/exams/new CExamNewR GET POST
|
/exams/new CExamNewR GET POST
|
||||||
/exams/#ExamName ExamR:
|
/exams/#ExamName ExamR:
|
||||||
@ -241,6 +246,8 @@
|
|||||||
/users/invite EInviteR GET POST
|
/users/invite EInviteR GET POST
|
||||||
/register ERegisterR POST !timeANDcourse-registeredAND¬exam-registered !timeANDexam-registeredAND¬exam-result
|
/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
|
/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
|
/grades EGradesR GET POST !exam-office
|
||||||
/assign-occurrences EAutoOccurrenceR POST
|
/assign-occurrences EAutoOccurrenceR POST
|
||||||
/correct ECorrectR GET POST !exam-correctorANDtime
|
/correct ECorrectR GET POST !exam-correctorANDtime
|
||||||
@ -253,9 +260,11 @@
|
|||||||
!/download/*FilePath CNFileR GET !timeANDparticipant
|
!/download/*FilePath CNFileR GET !timeANDparticipant
|
||||||
!/events/add CEventsNewR GET POST
|
!/events/add CEventsNewR GET POST
|
||||||
/events/#CryptoUUIDCourseEvent CourseEventR:
|
/events/#CryptoUUIDCourseEvent CourseEventR:
|
||||||
/edit CEvEditR GET POST
|
/schedule-opt/set/#Bool CEvScheduleOptSetR GET POST !free
|
||||||
/delete CEvDeleteR GET POST
|
/schedule-opt/delete CEvScheduleOptDelR GET POST !free
|
||||||
/personalised-sheet-files CPersonalFilesR GET
|
/edit CEvEditR GET POST
|
||||||
|
/delete CEvDeleteR GET POST
|
||||||
|
/personalised-sheet-files CPersonalFilesR GET
|
||||||
|
|
||||||
|
|
||||||
/subs CorrectionsR GET POST !corrector !lecturer
|
/subs CorrectionsR GET POST !corrector !lecturer
|
||||||
@ -270,6 +279,7 @@
|
|||||||
/msg/#{CryptoUUIDSystemMessage}/hide MessageHideR POST !timeANDauthentication
|
/msg/#{CryptoUUIDSystemMessage}/hide MessageHideR POST !timeANDauthentication
|
||||||
|
|
||||||
|
|
||||||
|
/schedule ScheduleR GET POST !free
|
||||||
/upload UploadR PUT !free
|
/upload UploadR PUT !free
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -144,6 +144,7 @@ import Handler.Tutorial
|
|||||||
import Handler.Material
|
import Handler.Material
|
||||||
import Handler.CryptoIDDispatch
|
import Handler.CryptoIDDispatch
|
||||||
import Handler.SystemMessage
|
import Handler.SystemMessage
|
||||||
|
import Handler.Schedule
|
||||||
import Handler.Health
|
import Handler.Health
|
||||||
import Handler.Health.Interface
|
import Handler.Health.Interface
|
||||||
import Handler.Exam
|
import Handler.Exam
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
-- 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-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-License-Identifier: AGPL-3.0-or-later
|
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ module Database.Esqueleto.Utils
|
|||||||
, SqlHashable
|
, SqlHashable
|
||||||
, sha256
|
, sha256
|
||||||
, isTrue, isFalse
|
, isTrue, isFalse
|
||||||
, maybe, maybe2, maybeEq, guardMaybe, unsafeCoalesce
|
, maybe, maybe2, maybeEq, fromMaybe, guardMaybe, unsafeCoalesce
|
||||||
, bool
|
, bool
|
||||||
, max, min
|
, max, min
|
||||||
, greatest, least
|
, greatest, least
|
||||||
@ -61,7 +61,7 @@ module Database.Esqueleto.Utils
|
|||||||
) where
|
) where
|
||||||
|
|
||||||
|
|
||||||
import ClassyPrelude.Yesod hiding (isInfixOf, any, all, or, and, isJust, maybe, bool, max, min, abs)
|
import ClassyPrelude.Yesod hiding (isInfixOf, any, all, or, and, isJust, maybe, fromMaybe, bool, max, min, abs)
|
||||||
import Data.Universe
|
import Data.Universe
|
||||||
import qualified Data.Set as Set
|
import qualified Data.Set as Set
|
||||||
import qualified Data.List as List
|
import qualified Data.List as List
|
||||||
@ -621,6 +621,13 @@ maybeEq a b = E.case_
|
|||||||
]
|
]
|
||||||
(E.else_ $ a E.==. b)
|
(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
|
guardMaybe :: PersistField a
|
||||||
=> E.SqlExpr (E.Value (Maybe a))
|
=> E.SqlExpr (E.Value (Maybe a))
|
||||||
-> E.SqlQuery (E.SqlExpr (E.Value a))
|
-> E.SqlQuery (E.SqlExpr (E.Value a))
|
||||||
|
|||||||
@ -61,6 +61,7 @@ import Data.CaseInsensitive (original, mk)
|
|||||||
import qualified Data.Text as Text
|
import qualified Data.Text as Text
|
||||||
|
|
||||||
import Utils.Form
|
import Utils.Form
|
||||||
|
import Utils.Schedule.Types.ScheduleView
|
||||||
|
|
||||||
import qualified GHC.Exts (IsList(..))
|
import qualified GHC.Exts (IsList(..))
|
||||||
|
|
||||||
@ -503,6 +504,13 @@ instance RenderMessage UniWorX CourseParticipantState where
|
|||||||
mr :: RenderMessage UniWorX msg => msg -> Text
|
mr :: RenderMessage UniWorX msg => msg -> Text
|
||||||
mr = renderMessage foundation ls
|
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
|
instance RenderMessage UniWorX ExamCloseMode where
|
||||||
renderMessage foundation ls = \case
|
renderMessage foundation ls = \case
|
||||||
ExamCloseSeparate -> mr MsgExamCloseModeSeparate
|
ExamCloseSeparate -> mr MsgExamCloseModeSeparate
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
-- SPDX-FileCopyrightText: 2022 Wolfgang Witt <Wolfgang.Witt@campus.lmu.de>
|
-- SPDX-FileCopyrightText: 2022-2024 Sarah Vaupel <sarah.vaupel@uniworx.de>, Wolfgang Witt <Wolfgang.Witt@campus.lmu.de>
|
||||||
--
|
--
|
||||||
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
@ -25,6 +25,7 @@ data instance ButtonClass UniWorX
|
|||||||
| BCDanger
|
| BCDanger
|
||||||
| BCLink
|
| BCLink
|
||||||
| BCMassInputAdd | BCMassInputDelete
|
| BCMassInputAdd | BCMassInputDelete
|
||||||
|
| BCScheduleView | BCScheduleOffset
|
||||||
deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic)
|
deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic)
|
||||||
deriving anyclass (Universe, Finite)
|
deriving anyclass (Universe, Finite)
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
-- 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-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-License-Identifier: AGPL-3.0-or-later
|
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
@ -265,6 +265,8 @@ 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 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 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 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 CNewsNewR) = i18nCrumb MsgMenuCourseNewsNew . Just $ CourseR tid ssh csh CShowR
|
||||||
breadcrumb (CourseR tid ssh csh (CourseNewsR cID sRoute)) = case sRoute of
|
breadcrumb (CourseR tid ssh csh (CourseNewsR cID sRoute)) = case sRoute of
|
||||||
@ -276,6 +278,8 @@ 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 CEventsNewR) = i18nCrumb MsgMenuCourseEventNew . Just $ CourseR tid ssh csh CShowR
|
||||||
breadcrumb (CourseR tid ssh csh (CourseEventR _cID sRoute)) = case sRoute of
|
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
|
CEvEditR -> i18nCrumb MsgMenuCourseEventEdit . Just $ CourseR tid ssh csh CShowR
|
||||||
CEvDeleteR -> i18nCrumb MsgBreadcrumbCourseEventDelete . Just $ CourseR tid ssh csh CShowR
|
CEvDeleteR -> i18nCrumb MsgBreadcrumbCourseEventDelete . Just $ CourseR tid ssh csh CShowR
|
||||||
|
|
||||||
@ -296,6 +300,8 @@ breadcrumb (CourseR tid ssh csh (ExamR examn sRoute)) = case sRoute of
|
|||||||
ERegisterOccR _occn -> i18nCrumb MsgBreadcrumbExamRegister . Just $ CExamR tid ssh csh examn EShowR
|
ERegisterOccR _occn -> i18nCrumb MsgBreadcrumbExamRegister . Just $ CExamR tid ssh csh examn EShowR
|
||||||
EAutoOccurrenceR -> i18nCrumb MsgBreadcrumbExamAutoOccurrence . Just $ CExamR tid ssh csh examn EUsersR
|
EAutoOccurrenceR -> i18nCrumb MsgBreadcrumbExamAutoOccurrence . Just $ CExamR tid ssh csh examn EUsersR
|
||||||
ECorrectR -> i18nCrumb MsgMenuExamCorrect . Just $ CExamR tid ssh csh examn EShowR
|
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
|
breadcrumb (CourseR tid ssh csh (TutorialR tutn sRoute)) = case sRoute of
|
||||||
TUsersR -> useRunDB . maybeT (i18nCrumb MsgBreadcrumbTutorial . Just $ CourseR tid ssh csh CTutorialListR) $ do
|
TUsersR -> useRunDB . maybeT (i18nCrumb MsgBreadcrumbTutorial . Just $ CourseR tid ssh csh CTutorialListR) $ do
|
||||||
@ -307,6 +313,8 @@ breadcrumb (CourseR tid ssh csh (TutorialR tutn sRoute)) = case sRoute of
|
|||||||
TCommR -> i18nCrumb MsgMenuTutorialComm . Just $ CTutorialR tid ssh csh tutn TUsersR
|
TCommR -> i18nCrumb MsgMenuTutorialComm . Just $ CTutorialR tid ssh csh tutn TUsersR
|
||||||
TRegisterR -> i18nCrumb MsgBreadcrumbTutorialRegister . Just $ CourseR tid ssh csh CShowR
|
TRegisterR -> i18nCrumb MsgBreadcrumbTutorialRegister . Just $ CourseR tid ssh csh CShowR
|
||||||
TInviteR -> i18nCrumb MsgBreadcrumbTutorInvite . Just $ CTutorialR tid ssh csh tutn TUsersR
|
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
|
breadcrumb (CourseR tid ssh csh (SheetR shn sRoute)) = case sRoute of
|
||||||
SShowR -> useRunDB . maybeT (i18nCrumb MsgBreadcrumbSheet . Just $ CourseR tid ssh csh SheetListR) $ do
|
SShowR -> useRunDB . maybeT (i18nCrumb MsgBreadcrumbSheet . Just $ CourseR tid ssh csh SheetListR) $ do
|
||||||
@ -369,6 +377,8 @@ breadcrumb (MessageR _) = do
|
|||||||
breadcrumb MessageListR = i18nCrumb MsgMenuMessageList $ Just AdminR
|
breadcrumb MessageListR = i18nCrumb MsgMenuMessageList $ Just AdminR
|
||||||
breadcrumb (MessageHideR cID) = i18nCrumb MsgBreadcrumbMessageHide . Just $ MessageR cID
|
breadcrumb (MessageHideR cID) = i18nCrumb MsgBreadcrumbMessageHide . Just $ MessageR cID
|
||||||
|
|
||||||
|
breadcrumb ScheduleR = i18nCrumb MsgMenuSchedule Nothing
|
||||||
|
|
||||||
breadcrumb GlossaryR = i18nCrumb MsgMenuGlossary $ Just InfoR
|
breadcrumb GlossaryR = i18nCrumb MsgMenuGlossary $ Just InfoR
|
||||||
|
|
||||||
breadcrumb EExamListR = i18nCrumb MsgMenuExternalExamList Nothing
|
breadcrumb EExamListR = i18nCrumb MsgMenuExternalExamList Nothing
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
-- 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-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-License-Identifier: AGPL-3.0-or-later
|
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
@ -303,6 +303,12 @@ decodeUser now UserDefaultConf{..} upsertMode ldapData = do
|
|||||||
, userPostLastUpdate = Nothing
|
, userPostLastUpdate = Nothing
|
||||||
, userPinPassword = Nothing -- must be derived via AVS
|
, userPinPassword = Nothing -- must be derived via AVS
|
||||||
, userPrefersPostal = userDefaultPrefersPostal
|
, userPrefersPostal = userDefaultPrefersPostal
|
||||||
|
, userScheduleView = userDefaultScheduleView
|
||||||
|
, userScheduleWeekDays = userDefaultScheduleWeekDays
|
||||||
|
, userScheduleWeekTimeFrom = userDefaultScheduleWeekTimeFrom
|
||||||
|
, userScheduleWeekTimeTo = userDefaultScheduleWeekTimeTo
|
||||||
|
, userScheduleWeekTimeslotLength = userDefaultScheduleWeekTimeslotLength
|
||||||
|
, userScheduleOccurrenceDisplayDefault = userDefaultScheduleOccurrenceDisplayDefault
|
||||||
, ..
|
, ..
|
||||||
}
|
}
|
||||||
userUpdate =
|
userUpdate =
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import Handler.ExamOffice.Course as Handler.Course
|
|||||||
import Handler.Course.News as Handler.Course
|
import Handler.Course.News as Handler.Course
|
||||||
import Handler.Course.Events as Handler.Course
|
import Handler.Course.Events as Handler.Course
|
||||||
import Handler.Sheet.PersonalisedFiles as Handler.Course (getCPersonalFilesR)
|
import Handler.Sheet.PersonalisedFiles as Handler.Course (getCPersonalFilesR)
|
||||||
|
import Handler.Course.Schedule as Handler.Course
|
||||||
|
|
||||||
|
|
||||||
getCHiWisR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
|
getCHiWisR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
|
||||||
|
|||||||
@ -9,3 +9,4 @@ module Handler.Course.Events
|
|||||||
import Handler.Course.Events.New as 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.Edit as Handler.Course.Events
|
||||||
import Handler.Course.Events.Delete as Handler.Course.Events
|
import Handler.Course.Events.Delete as Handler.Course.Events
|
||||||
|
import Handler.Course.Events.Schedule as Handler.Course.Events
|
||||||
|
|||||||
36
src/Handler/Course/Events/Schedule.hs
Normal file
36
src/Handler/Course/Events/Schedule.hs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
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
|
||||||
41
src/Handler/Course/Schedule.hs
Normal file
41
src/Handler/Course/Schedule.hs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
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,6 +10,7 @@ import Import
|
|||||||
|
|
||||||
import Utils.Course
|
import Utils.Course
|
||||||
import Utils.Form
|
import Utils.Form
|
||||||
|
import Utils.Schedule
|
||||||
import Handler.Utils
|
import Handler.Utils
|
||||||
import Handler.Utils.Course
|
import Handler.Utils.Course
|
||||||
import Handler.Utils.Tutorial
|
import Handler.Utils.Tutorial
|
||||||
@ -29,14 +30,16 @@ import Handler.Exam.List (mkExamTable)
|
|||||||
|
|
||||||
getCShowR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
|
getCShowR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
|
||||||
getCShowR tid ssh csh = do
|
getCShowR tid ssh csh = do
|
||||||
mbAid <- maybeAuthId
|
mbAuth <- maybeAuthPair
|
||||||
now <- liftIO getCurrentTime
|
now <- liftIO getCurrentTime
|
||||||
(cid,course,courseVisible,schoolName,participants,registration,lecturers,assistants,administrators,correctors,tutors,news,events,submissionGroup,_mayReRegister,(mayViewSheets, mayViewAnySheet), (mayViewMaterials, mayViewAnyMaterial),courseQualifications) <- runDB . maybeT notFound $ do
|
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
|
||||||
[(E.Entity cid course, E.Value courseVisible, E.Value schoolName, E.Value participants, fmap entityVal -> registration)]
|
[(E.Entity cid course, E.Value courseVisible, E.Value schoolName, E.Value participants, fmap entityVal -> registration)]
|
||||||
<- lift . E.select . E.from $
|
<- lift . E.select . E.from $
|
||||||
\((school `E.InnerJoin` course) `E.LeftOuterJoin` participant) -> do
|
\((school `E.InnerJoin` course) `E.LeftOuterJoin` participant) -> do
|
||||||
E.on $ E.just (course E.^. CourseId) E.==. participant E.?. CourseParticipantCourse
|
E.on $ E.just (course E.^. CourseId) E.==. participant E.?. CourseParticipantCourse
|
||||||
E.&&. E.val mbAid E.==. participant E.?. CourseParticipantUser
|
E.&&. E.val (fst <$> mbAuth) E.==. participant E.?. CourseParticipantUser
|
||||||
E.&&. participant E.?. CourseParticipantState E.==. E.just (E.val CourseParticipantActive)
|
E.&&. participant E.?. CourseParticipantState E.==. E.just (E.val CourseParticipantActive)
|
||||||
E.on $ course E.^. CourseSchool E.==. school E.^. SchoolId
|
E.on $ course E.^. CourseSchool E.==. school E.^. SchoolId
|
||||||
E.where_ $ course E.^. CourseTerm E.==. E.val tid
|
E.where_ $ course E.^. CourseTerm E.==. E.val tid
|
||||||
@ -96,17 +99,30 @@ getCShowR tid ssh csh = do
|
|||||||
|
|
||||||
return (cID, n, visible, files, lastEditText, mayEditNews, mayDelete, archiveUrl)
|
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
|
events' <- fmap (sortOn $ courseEventTime . entityVal . view _1) . lift . E.select . E.from $ \courseEvent -> do
|
||||||
E.where_ $ courseEvent E.^. CourseEventCourse E.==. E.val cid
|
E.where_ $ courseEvent E.^. CourseEventCourse E.==. E.val cid
|
||||||
let showRoom = maybe E.false (flip showCourseEventRoom courseEvent . E.val) mbAid
|
let showRoom = maybe E.false (flip showCourseEventRoom courseEvent . E.val . view _1) mbAuth
|
||||||
E.||. E.not_ (courseEvent E.^. CourseEventRoomHidden)
|
E.||. E.not_ (courseEvent E.^. CourseEventRoomHidden)
|
||||||
return (courseEvent, showRoom)
|
return (courseEvent, showRoom)
|
||||||
events <- mapM (\(Entity evId ev, E.Value showRoom) -> (, ev, showRoom) <$> encrypt evId) events'
|
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)
|
||||||
|
|
||||||
hasSubmissionGroups <- lift . E.selectExists . E.from $ \(submissionGroupUser `E.InnerJoin` submissionGroup) -> do
|
hasSubmissionGroups <- lift . E.selectExists . E.from $ \(submissionGroupUser `E.InnerJoin` submissionGroup) -> do
|
||||||
E.on $ submissionGroupUser E.^. SubmissionGroupUserSubmissionGroup E.==. submissionGroup E.^. SubmissionGroupId
|
E.on $ submissionGroupUser E.^. SubmissionGroupUserSubmissionGroup E.==. submissionGroup E.^. SubmissionGroupId
|
||||||
E.where_ $ submissionGroup E.^. SubmissionGroupCourse E.==. E.val cid
|
E.where_ $ submissionGroup E.^. SubmissionGroupCourse E.==. E.val cid
|
||||||
submissionGroup' <- lift . for mbAid $ \uid ->
|
submissionGroup' <- lift . for mbAuth $ \(uid,_) ->
|
||||||
fmap (listToMaybe . fmap E.unValue) . E.select . E.from $ \(submissionGroupUser `E.InnerJoin` submissionGroup) -> do
|
fmap (listToMaybe . fmap E.unValue) . E.select . E.from $ \(submissionGroupUser `E.InnerJoin` submissionGroup) -> do
|
||||||
E.on $ submissionGroupUser E.^. SubmissionGroupUserSubmissionGroup E.==. submissionGroup E.^. SubmissionGroupId
|
E.on $ submissionGroupUser E.^. SubmissionGroupUserSubmissionGroup E.==. submissionGroup E.^. SubmissionGroupId
|
||||||
E.where_ $ submissionGroup E.^. SubmissionGroupCourse E.==. E.val cid
|
E.where_ $ submissionGroup E.^. SubmissionGroupCourse E.==. E.val cid
|
||||||
@ -128,14 +144,14 @@ getCShowR tid ssh csh = do
|
|||||||
return $ material E.^. MaterialName
|
return $ material E.^. MaterialName
|
||||||
mayViewAnyMaterial <- lift . anyM materials $ \(E.Value mnm) -> hasReadAccessTo $ CMaterialR tid ssh csh mnm MShowR
|
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, (mayViewSheets, mayViewAnySheet), (mayViewMaterials, mayViewAnyMaterial), courseQualifications)
|
return (cid,course,courseVisible,schoolName,participants,registration,lecturers,assistants,administrators,correctors,tutors,news,events,submissionGroup,mayReRegister,mCourseScheduleOpt,mayReRegister,(mayViewSheets,mayViewAnySheet),(mayViewMaterials,mayViewAnyMaterial),courseQualifications)
|
||||||
|
|
||||||
mDereg <- traverse (formatTime SelFormatDateTime) $ courseDeregisterUntil course
|
mDereg <- traverse (formatTime SelFormatDateTime) $ courseDeregisterUntil course
|
||||||
|
|
||||||
regForm <- if
|
regForm <- if
|
||||||
| is _Just mbAid -> do
|
| is _Just mbAuth -> do
|
||||||
(courseRegisterForm', regButton) <- courseRegisterForm (Entity cid course)
|
(courseRegisterForm', regButton) <- courseRegisterForm (Entity cid course)
|
||||||
(regWidget, regEnctype) <- generateFormPost $ renderAForm FormStandard courseRegisterForm'
|
(regWidget, regEnctype) <- generateFormPost $ renderAForm FormStandard courseRegisterForm'
|
||||||
return $ wrapForm' regButton regWidget def
|
return $ wrapForm' regButton regWidget def
|
||||||
@ -159,7 +175,7 @@ getCShowR tid ssh csh = do
|
|||||||
|
|
||||||
dbtSQLQuery tutorial = do
|
dbtSQLQuery tutorial = do
|
||||||
E.where_ $ tutorial E.^. TutorialCourse E.==. E.val cid
|
E.where_ $ tutorial E.^. TutorialCourse E.==. E.val cid
|
||||||
let showRoom = maybe E.false (flip showTutorialRoom tutorial . E.val) mbAid
|
let showRoom = maybe E.false (flip showTutorialRoom tutorial . E.val . view _1) mbAuth
|
||||||
E.||. E.not_ (tutorial E.^. TutorialRoomHidden)
|
E.||. E.not_ (tutorial E.^. TutorialRoomHidden)
|
||||||
return (tutorial, showRoom)
|
return (tutorial, showRoom)
|
||||||
dbtRowKey = (E.^. TutorialId)
|
dbtRowKey = (E.^. TutorialId)
|
||||||
@ -197,22 +213,49 @@ getCShowR tid ssh csh = do
|
|||||||
in return $ E.val tutorialCapacity' E.-. numParticipants
|
in return $ E.val tutorialCapacity' E.-. numParticipants
|
||||||
return . toWidget $ tshow freeCapacity
|
return . toWidget $ tshow freeCapacity
|
||||||
, guardMonoid (not mayMassRegister || isJust registration) $
|
, guardMonoid (not mayMassRegister || isJust registration) $
|
||||||
sortable Nothing (mempty & cellAttrs <>~ pure ("uw-hide-columns--hider-label", mr MsgTableActionsHead)) $ \(view resultTutorial -> Entity tutId Tutorial{..}) -> sqlCell $ do
|
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
|
mayRegister <- (== Authorized) <$> evalAccessDB (CTutorialR tid ssh csh tutorialName TRegisterR) True
|
||||||
isRegistered <- case mbAid of
|
isRegistered <- case mbAuth of
|
||||||
Nothing -> return False
|
Nothing -> return False
|
||||||
Just uid -> existsBy $ UniqueTutorialParticipant tutId uid
|
Just (uid,_) -> existsBy $ UniqueTutorialParticipant tutId uid
|
||||||
if
|
tutRegister <- if
|
||||||
| mayRegister -> do
|
| mayRegister -> do
|
||||||
(tutRegisterForm, tutRegisterEnctype) <- liftHandler . generateFormPost . buttonForm' $ bool [BtnRegister] [BtnDeregister] isRegistered
|
(tutRegisterForm, tutRegisterEnctype) <- liftHandler . generateFormPost . buttonForm' $ bool [BtnRegister] [BtnDeregister] isRegistered
|
||||||
return $ wrapForm tutRegisterForm def
|
return $ wrapForm tutRegisterForm def
|
||||||
{ formAction = Just . SomeRoute $ CTutorialR tid ssh csh tutorialName TRegisterR
|
{ formAction = Just . SomeRoute $ CTutorialR tid ssh csh tutorialName TRegisterR
|
||||||
, formEncoding = tutRegisterEnctype
|
, formEncoding = tutRegisterEnctype
|
||||||
, formSubmit = FormNoSubmit
|
, formSubmit = FormNoSubmit
|
||||||
}
|
}
|
||||||
| isRegistered -> return [whamlet|_{MsgTutorialRegistered}|]
|
| isRegistered -> return [whamlet|_{MsgTutorialRegistered}|]
|
||||||
| otherwise -> return mempty
|
| otherwise -> return mempty
|
||||||
, guardMonoid mayMassRegister $
|
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 . _entityVal -> Tutorial{..}) ->
|
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)
|
cell $ linkButton mempty (msg2widget MsgMassRegister) [BCIsButton, BCPrimary] (SomeRoute $ CTutorialR tid ssh csh tutorialName TAddUserR)
|
||||||
]
|
]
|
||||||
@ -253,12 +296,19 @@ getCShowR tid ssh csh = do
|
|||||||
, length fs <= 3
|
, length fs <= 3
|
||||||
, all (views (_1 . _2) $ notElem pathSeparator) fs
|
, 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
|
Course{courseVisibleFrom,courseVisibleTo} = course
|
||||||
mayCreateNews <- hasWriteAccessTo $ CourseR tid ssh csh CNewsNewR
|
mayCreateNews <- hasWriteAccessTo $ CourseR tid ssh csh CNewsNewR
|
||||||
mayCreateEvents <- hasWriteAccessTo $ CourseR tid ssh csh CEventsNewR
|
mayCreateEvents <- hasWriteAccessTo $ CourseR tid ssh csh CEventsNewR
|
||||||
mayEdit <- hasWriteAccessTo $ CourseR tid ssh csh CEditR
|
mayEdit <- hasWriteAccessTo $ CourseR tid ssh csh CEditR
|
||||||
|
|
||||||
|
let courseScheduleOptToggleValue User{userScheduleOccurrenceDisplayDefault} = maybe
|
||||||
|
( userScheduleOccurrenceDisplayDefault
|
||||||
|
&& ( is _Just registration )
|
||||||
|
)
|
||||||
|
(courseScheduleOptOpt . entityVal)
|
||||||
|
mCourseScheduleOpt
|
||||||
|
|
||||||
let heading = [whamlet|
|
let heading = [whamlet|
|
||||||
$newline never
|
$newline never
|
||||||
^{courseName course}
|
^{courseName course}
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import Handler.Exam.RegistrationInvite as Handler.Exam
|
|||||||
import Handler.Exam.New as Handler.Exam
|
import Handler.Exam.New as Handler.Exam
|
||||||
import Handler.Exam.Edit as Handler.Exam
|
import Handler.Exam.Edit as Handler.Exam
|
||||||
import Handler.Exam.Show 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.Users as Handler.Exam
|
||||||
import Handler.Exam.AddUser as Handler.Exam
|
import Handler.Exam.AddUser as Handler.Exam
|
||||||
import Handler.Exam.AutoOccurrence as Handler.Exam
|
import Handler.Exam.AutoOccurrence as Handler.Exam
|
||||||
|
|||||||
45
src/Handler/Exam/Schedule.hs
Normal file
45
src/Handler/Exam/Schedule.hs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
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,11 +24,14 @@ import qualified Data.CaseInsensitive as CI
|
|||||||
import Handler.Utils
|
import Handler.Utils
|
||||||
import Handler.Utils.Exam
|
import Handler.Utils.Exam
|
||||||
|
|
||||||
|
import Utils.Schedule
|
||||||
|
|
||||||
|
|
||||||
getEShowR :: TermId -> SchoolId -> CourseShorthand -> ExamName -> Handler Html
|
getEShowR :: TermId -> SchoolId -> CourseShorthand -> ExamName -> Handler Html
|
||||||
getEShowR tid ssh csh examn = do
|
getEShowR tid ssh csh examn = do
|
||||||
cTime <- liftIO getCurrentTime
|
cTime <- liftIO getCurrentTime
|
||||||
mUid <- maybeAuthId
|
mAuth <- maybeAuth
|
||||||
|
ata <- getSessionActiveAuthTags
|
||||||
|
|
||||||
(Entity eId Exam{..}, School{..}, examParts, examVisible, (gradingVisible, gradingShown), (occurrenceAssignmentsVisible, occurrenceAssignmentsShown), results, result, bonus, occurrences, (registered, mayRegister), registeredCount, lecturerInfoShown, staffInfoShown, extraSchools, (partsVisible, partsShown)) <- runDB $ do
|
(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
|
exam@(Entity eId Exam{..}) <- fetchExam tid ssh csh examn
|
||||||
@ -54,21 +57,27 @@ getEShowR tid ssh csh examn = do
|
|||||||
flip filterM sheets' $ \(Entity _ Sheet{..}) -> hasReadAccessTo $ CSheetR tid ssh csh sheetName SShowR
|
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)
|
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 mUid $ \uid ->
|
resultsRaw <- for mAuth $ \(Entity uid _) -> E.select . E.from $ \examPartResult -> do
|
||||||
E.select . E.from $ \examPartResult -> do
|
|
||||||
E.where_ $ examPartResult E.^. ExamPartResultUser E.==. E.val uid
|
E.where_ $ examPartResult E.^. ExamPartResultUser E.==. E.val uid
|
||||||
E.&&. examPartResult E.^. ExamPartResultExamPart `E.in_` E.valList (map (views _1 entityKey) examParts)
|
E.&&. examPartResult E.^. ExamPartResultExamPart `E.in_` E.valList (map (views _1 entityKey) examParts)
|
||||||
return examPartResult
|
return examPartResult
|
||||||
let results = maybe Map.empty (\rs -> Map.fromList [ (examPartResultExamPart, res) | res@(Entity _ ExamPartResult{..}) <- rs ]) resultsRaw
|
let results = maybe Map.empty (\rs -> Map.fromList [ (examPartResultExamPart, res) | res@(Entity _ ExamPartResult{..}) <- rs ]) resultsRaw
|
||||||
|
|
||||||
result <- fmap join . for mUid $ getBy . UniqueExamResult eId
|
result <- fmap join . for mAuth $ getBy . UniqueExamResult eId . entityKey
|
||||||
bonus <- fmap join . for mUid $ getBy . UniqueExamBonus eId
|
bonus <- fmap join . for mAuth $ getBy . UniqueExamBonus eId . entityKey
|
||||||
|
|
||||||
occurrencesRaw <- E.select . E.from $ \examOccurrence -> do
|
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
|
||||||
E.where_ $ examOccurrence E.^. ExamOccurrenceExam E.==. E.val eId
|
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
|
let
|
||||||
|
shouldBeDisplayedInSchedule = examOccurrenceShouldBeDisplayedInSchedule (entityKey <$> mAuth) ata cTime course ex examOccurrence
|
||||||
registered
|
registered
|
||||||
| Just uid <- mUid
|
| Just (Entity uid _) <- mAuth
|
||||||
= E.exists . E.from $ \examRegistration ->
|
= E.exists . E.from $ \examRegistration ->
|
||||||
E.where_ $ examRegistration E.^. ExamRegistrationExam E.==. E.val eId
|
E.where_ $ examRegistration E.^. ExamRegistrationExam E.==. E.val eId
|
||||||
E.&&. examRegistration E.^. ExamRegistrationUser E.==. E.val uid
|
E.&&. examRegistration E.^. ExamRegistrationUser E.==. E.val uid
|
||||||
@ -79,22 +88,22 @@ getEShowR tid ssh csh examn = do
|
|||||||
= E.subSelectCount . E.from $ \examRegistration ->
|
= E.subSelectCount . E.from $ \examRegistration ->
|
||||||
E.where_ $ examRegistration E.^. ExamRegistrationExam E.==. E.val eId
|
E.where_ $ examRegistration E.^. ExamRegistrationExam E.==. E.val eId
|
||||||
E.&&. examRegistration E.^. ExamRegistrationOccurrence E.==. E.just (examOccurrence E.^. ExamOccurrenceId)
|
E.&&. examRegistration E.^. ExamRegistrationOccurrence E.==. E.just (examOccurrence E.^. ExamOccurrenceId)
|
||||||
showRoom = maybe E.false (flip showExamOccurrenceRoom examOccurrence . E.val) mUid
|
showRoom = maybe E.false (flip showExamOccurrenceRoom examOccurrence . E.val . entityKey) mAuth
|
||||||
E.||. E.not_ (examOccurrence E.^. ExamOccurrenceRoomHidden)
|
E.||. E.not_ (examOccurrence E.^. ExamOccurrenceRoomHidden)
|
||||||
E.orderBy [E.desc registered, E.asc $ examOccurrence E.^. ExamOccurrenceStart, E.asc $ examOccurrence E.^. ExamOccurrenceRoom]
|
E.orderBy [E.desc registered, E.asc $ examOccurrence E.^. ExamOccurrenceStart, E.asc $ examOccurrence E.^. ExamOccurrenceRoom]
|
||||||
return (examOccurrence, registered, registeredCount, showRoom)
|
return (examOccurrence, registered, registeredCount, showRoom, shouldBeDisplayedInSchedule, examOccurrenceScheduleOpt)
|
||||||
|
|
||||||
registeredCount <- fromIntegral <$> count [ ExamRegistrationExam ==. eId ]
|
registeredCount <- fromIntegral <$> count [ ExamRegistrationExam ==. eId ]
|
||||||
|
|
||||||
registered <- for mUid $ getBy . UniqueExamRegistration eId
|
registered <- for mAuth $ getBy . UniqueExamRegistration eId . entityKey
|
||||||
mayRegister <- if
|
mayRegister <- if
|
||||||
| examOccurrenceRule == ExamRoomFifo -> anyM occurrencesRaw $ \(Entity _ ExamOccurrence{..}, _, _, _) ->
|
| examOccurrenceRule == ExamRoomFifo -> anyM occurrencesRaw $ \(Entity _ ExamOccurrence{..}, _, _, _, _, _) ->
|
||||||
hasWriteAccessTo . CExamR tid ssh csh examName $ ERegisterOccR examOccurrenceName
|
hasWriteAccessTo . CExamR tid ssh csh examName $ ERegisterOccR examOccurrenceName
|
||||||
| otherwise -> hasWriteAccessTo $ CExamR tid ssh csh examName ERegisterR
|
| otherwise -> hasWriteAccessTo $ CExamR tid ssh csh examName ERegisterR
|
||||||
|
|
||||||
let occurrences = sortOn sortPred $ map (over _4 E.unValue . over _3 E.unValue . over _2 E.unValue) occurrencesRaw
|
let occurrences = sortOn sortPred $ map (over _5 E.unValue . over _4 E.unValue . over _3 E.unValue . over _2 E.unValue) occurrencesRaw
|
||||||
where
|
where
|
||||||
sortPred (Entity _ ExamOccurrence{..}, registered', _, showRoom)
|
sortPred (Entity _ ExamOccurrence{..}, registered', _, showRoom, _, _)
|
||||||
= (Down $ registered' && not mayRegister, examOccurrenceStart, guardOn @Maybe showRoom examOccurrenceRoom)
|
= (Down $ registered' && not mayRegister, examOccurrenceStart, guardOn @Maybe showRoom examOccurrenceRoom)
|
||||||
|
|
||||||
staffInfoShown <- hasReadAccessTo $ CExamR tid ssh csh examn EGradesR
|
staffInfoShown <- hasReadAccessTo $ CExamR tid ssh csh examn EGradesR
|
||||||
@ -133,13 +142,13 @@ getEShowR tid ssh csh examn = do
|
|||||||
mayRegister' <- fmap ((Map.!) . Map.fromList) . for (Nothing : map Just occurrences) $ \case
|
mayRegister' <- fmap ((Map.!) . Map.fromList) . for (Nothing : map Just occurrences) $ \case
|
||||||
Nothing ->
|
Nothing ->
|
||||||
fmap (Nothing, ) . hasWriteAccessTo $ CExamR tid ssh csh examName ERegisterR
|
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
|
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
|
examRoom = do
|
||||||
(Entity _ primeOcc, _, _, _) <- occurrences ^? _head
|
(Entity _ primeOcc, _, _, _, _, _) <- occurrences ^? _head
|
||||||
guard $ all (\(Entity _ occ, _, _, _) -> examOccurrenceRoom occ == examOccurrenceRoom primeOcc) occurrences
|
guard $ all (\(Entity _ occ, _, _, _, _, _) -> examOccurrenceRoom occ == examOccurrenceRoom primeOcc) occurrences
|
||||||
guard $ andOf (folded . _4) occurrences
|
guard $ andOf (folded . _4) occurrences
|
||||||
examOccurrenceRoom primeOcc
|
examOccurrenceRoom primeOcc
|
||||||
registerWidget mOcc
|
registerWidget mOcc
|
||||||
|
|||||||
@ -24,22 +24,27 @@ import qualified Data.Conduit.Lift as C
|
|||||||
|
|
||||||
import qualified Data.HashMap.Strict as HashMap
|
import qualified Data.HashMap.Strict as HashMap
|
||||||
|
|
||||||
|
-- TODO: deprecated
|
||||||
|
import Utils.Schedule.Types (ScheduleOffset(..))
|
||||||
|
import Utils.Schedule.Week
|
||||||
|
|
||||||
import Handler.Utils.Exam (showExamOccurrenceRoom)
|
import Handler.Utils.Exam (showExamOccurrenceRoom)
|
||||||
|
|
||||||
|
|
||||||
getNewsR :: Handler Html
|
getNewsR :: Handler Html
|
||||||
getNewsR = do
|
getNewsR = do
|
||||||
muid <- maybeAuthId
|
mUser <- maybeAuth
|
||||||
defaultLayout $ do
|
defaultLayout $ do
|
||||||
setTitleI MsgNewsHeading
|
setTitleI MsgNewsHeading
|
||||||
|
|
||||||
newsSystemMessages
|
newsSystemMessages
|
||||||
|
|
||||||
when (is _Nothing muid) $
|
when (is _Nothing mUser) $
|
||||||
notificationWidget NotificationBroad Info $(i18nWidgetFile "pitch")
|
notificationWidget NotificationBroad Info $(i18nWidgetFile "pitch")
|
||||||
|
|
||||||
case muid of
|
case mUser of
|
||||||
Just uid -> do
|
Just user@(Entity uid _) -> do
|
||||||
|
newsSchedule user
|
||||||
newsUpcomingExams uid
|
newsUpcomingExams uid
|
||||||
newsUpcomingSheets uid
|
newsUpcomingSheets uid
|
||||||
Nothing ->
|
Nothing ->
|
||||||
@ -93,6 +98,14 @@ newsSystemMessages = do
|
|||||||
$(widgetFile "news/system-messages")
|
$(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 :: UserId -> Widget
|
||||||
newsUpcomingSheets uid = do
|
newsUpcomingSheets uid = do
|
||||||
cTime <- liftIO getCurrentTime
|
cTime <- liftIO getCurrentTime
|
||||||
|
|||||||
@ -44,8 +44,11 @@ import qualified Data.CaseInsensitive as CI
|
|||||||
|
|
||||||
import Jobs
|
import Jobs
|
||||||
|
|
||||||
|
import Foundation.I18n ()
|
||||||
import Foundation.Yesod.Auth (updateUserLanguage)
|
import Foundation.Yesod.Auth (updateUserLanguage)
|
||||||
|
|
||||||
|
import Utils.Schedule.Types.ScheduleView
|
||||||
|
|
||||||
|
|
||||||
{-# ANN module ("HLint: ignore Functor law" :: String) #-}
|
{-# ANN module ("HLint: ignore Functor law" :: String) #-}
|
||||||
|
|
||||||
@ -67,12 +70,19 @@ type EOLabels = Map (Either ExamOfficeLabelName ExamOfficeLabelId) EOLabelData
|
|||||||
data SettingsForm = SettingsForm
|
data SettingsForm = SettingsForm
|
||||||
{ stgDisplayName :: UserDisplayName
|
{ stgDisplayName :: UserDisplayName
|
||||||
, stgDisplayEmail :: UserEmail
|
, stgDisplayEmail :: UserEmail
|
||||||
|
, stgScheduleView :: ScheduleView
|
||||||
|
, stgScheduleWeekDays :: ScheduleWeekDays
|
||||||
|
, stgScheduleWeekTimeFrom
|
||||||
|
, stgScheduleWeekTimeTo :: NominalDiffTime
|
||||||
|
, stgScheduleWeekTimeslotLength :: NominalDiffTime
|
||||||
|
, stgScheduleOccurrenceDisplayDefault :: Bool
|
||||||
, stgMaxFavourites :: Int
|
, stgMaxFavourites :: Int
|
||||||
, stgMaxFavouriteTerms :: Int
|
, stgMaxFavouriteTerms :: Int
|
||||||
, stgTheme :: Theme
|
, stgTheme :: Theme
|
||||||
, stgDateTime :: DateTimeFormat
|
, stgDateTime :: DateTimeFormat
|
||||||
, stgDate :: DateTimeFormat
|
, stgDate :: DateTimeFormat
|
||||||
, stgTime :: DateTimeFormat
|
, stgTime :: DateTimeFormat
|
||||||
|
, stgWeekStart :: DayOfWeek
|
||||||
, stgDownloadFiles :: Bool
|
, stgDownloadFiles :: Bool
|
||||||
, stgWarningDays :: NominalDiffTime
|
, stgWarningDays :: NominalDiffTime
|
||||||
, stgShowSex :: Bool
|
, stgShowSex :: Bool
|
||||||
@ -122,8 +132,21 @@ makeSettingForm template html = do
|
|||||||
-- isAdmin <- checkAdmin
|
-- isAdmin <- checkAdmin
|
||||||
(result, widget) <- flip (renderAForm FormStandard) html $ SettingsForm
|
(result, widget) <- flip (renderAForm FormStandard) html $ SettingsForm
|
||||||
<$ aformSection MsgFormPersonalAppearance
|
<$ 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)
|
<*> 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
|
<* aformSection MsgFormCosmetics
|
||||||
<*> areq (natFieldI MsgFavouritesNotNatural)
|
<*> areq (natFieldI MsgFavouritesNotNatural)
|
||||||
(fslpI MsgFavourites (mr MsgFavouritesPlaceholder) & setTooltip MsgFavouritesTip) (stgMaxFavourites <$> template)
|
(fslpI MsgFavourites (mr MsgFavouritesPlaceholder) & setTooltip MsgFavouritesTip) (stgMaxFavourites <$> template)
|
||||||
@ -134,6 +157,7 @@ makeSettingForm template html = do
|
|||||||
<*> areq (selectField $ dateTimeFormatOptions SelFormatDateTime) (fslI MsgDateTimeFormat) (stgDateTime <$> template)
|
<*> areq (selectField $ dateTimeFormatOptions SelFormatDateTime) (fslI MsgDateTimeFormat) (stgDateTime <$> template)
|
||||||
<*> areq (selectField $ dateTimeFormatOptions SelFormatDate) (fslI MsgDateFormat) (stgDate <$> template)
|
<*> areq (selectField $ dateTimeFormatOptions SelFormatDate) (fslI MsgDateFormat) (stgDate <$> template)
|
||||||
<*> areq (selectField $ dateTimeFormatOptions SelFormatTime) (fslI MsgTimeFormat) (stgTime <$> template)
|
<*> areq (selectField $ dateTimeFormatOptions SelFormatTime) (fslI MsgTimeFormat) (stgTime <$> template)
|
||||||
|
<*> areq (selectField optionsFinite) (fslI MsgWeekStart) (stgWeekStart <$> template)
|
||||||
<* aformSection MsgFormBehaviour
|
<* aformSection MsgFormBehaviour
|
||||||
<*> apopt checkBoxField (fslI MsgDownloadFiles
|
<*> apopt checkBoxField (fslI MsgDownloadFiles
|
||||||
& setTooltip MsgDownloadFilesTip
|
& setTooltip MsgDownloadFilesTip
|
||||||
@ -156,7 +180,12 @@ makeSettingForm template html = do
|
|||||||
<*> notificationForm (stgNotificationSettings <$> template)
|
<*> notificationForm (stgNotificationSettings <$> template)
|
||||||
return (result, widget) -- no validation here, done later by validateSettings
|
return (result, widget) -- no validation here, done later by validateSettings
|
||||||
where
|
where
|
||||||
themeList = [Option (toMessage t) t (toPathPiece t) | t <- universeF]
|
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
|
||||||
|
|
||||||
schoolsForm :: Maybe (Set SchoolId) -> AForm Handler (Set SchoolId)
|
schoolsForm :: Maybe (Set SchoolId) -> AForm Handler (Set SchoolId)
|
||||||
schoolsForm template = formToAForm $ schoolsFormView =<< renderWForm FormStandard schoolsForm' mempty
|
schoolsForm template = formToAForm $ schoolsFormView =<< renderWForm FormStandard schoolsForm' mempty
|
||||||
@ -375,6 +404,11 @@ validateSettings User{..} = do
|
|||||||
userDisplayName == userDisplayName' || -- unchanged or valid (invalid displayNames delivered by LDAP are preserved)
|
userDisplayName == userDisplayName' || -- unchanged or valid (invalid displayNames delivered by LDAP are preserved)
|
||||||
validDisplayName userTitle userFirstName userSurname userDisplayName'
|
validDisplayName userTitle userFirstName userSurname userDisplayName'
|
||||||
|
|
||||||
|
userScheduleWeekTimeFrom' <- use _stgScheduleWeekTimeFrom
|
||||||
|
userScheduleWeekTimeTo' <- use _stgScheduleWeekTimeTo
|
||||||
|
|
||||||
|
guardValidation MsgScheduleWeekTimeToMustBeAfterTimeFrom
|
||||||
|
$ userScheduleWeekTimeTo' > userScheduleWeekTimeFrom'
|
||||||
userDisplayEmail' <- use _stgDisplayEmail
|
userDisplayEmail' <- use _stgDisplayEmail
|
||||||
guardValidation (MsgInvalidEmailAddressWith $ CI.original userDisplayEmail') $
|
guardValidation (MsgInvalidEmailAddressWith $ CI.original userDisplayEmail') $
|
||||||
validEmail' userDisplayEmail' || -- valid
|
validEmail' userDisplayEmail' || -- valid
|
||||||
@ -445,12 +479,19 @@ serveProfileR (uid, user@User{..}) = do
|
|||||||
let settingsTemplate = Just SettingsForm
|
let settingsTemplate = Just SettingsForm
|
||||||
{ stgDisplayName = userDisplayName
|
{ stgDisplayName = userDisplayName
|
||||||
, stgDisplayEmail = if userDisplayEmail == "" then userEmail else userDisplayEmail
|
, stgDisplayEmail = if userDisplayEmail == "" then userEmail else userDisplayEmail
|
||||||
|
, stgScheduleView = userScheduleView
|
||||||
|
, stgScheduleWeekDays = userScheduleWeekDays
|
||||||
|
, stgScheduleWeekTimeFrom = userScheduleWeekTimeFrom
|
||||||
|
, stgScheduleWeekTimeTo = userScheduleWeekTimeTo
|
||||||
|
, stgScheduleWeekTimeslotLength = userScheduleWeekTimeslotLength
|
||||||
|
, stgScheduleOccurrenceDisplayDefault = userScheduleOccurrenceDisplayDefault
|
||||||
, stgMaxFavourites = userMaxFavourites
|
, stgMaxFavourites = userMaxFavourites
|
||||||
, stgMaxFavouriteTerms = userMaxFavouriteTerms
|
, stgMaxFavouriteTerms = userMaxFavouriteTerms
|
||||||
, stgTheme = userTheme
|
, stgTheme = userTheme
|
||||||
, stgDateTime = userDateTimeFormat
|
, stgDateTime = userDateTimeFormat
|
||||||
, stgDate = userDateFormat
|
, stgDate = userDateFormat
|
||||||
, stgTime = userTimeFormat
|
, stgTime = userTimeFormat
|
||||||
|
, stgWeekStart = userWeekStart
|
||||||
, stgDownloadFiles = userDownloadFiles
|
, stgDownloadFiles = userDownloadFiles
|
||||||
, stgSchools = userSchools
|
, stgSchools = userSchools
|
||||||
, stgNotificationSettings = userNotificationSettings
|
, stgNotificationSettings = userNotificationSettings
|
||||||
@ -480,12 +521,19 @@ serveProfileR (uid, user@User{..}) = do
|
|||||||
[ UserDisplayEmail =. stgDisplayEmail | not changeEmailByUser, changeEmailProper ] ++ -- DisplayEmail changes by Users must be confirmed, see 480 below
|
[ UserDisplayEmail =. stgDisplayEmail | not changeEmailByUser, changeEmailProper ] ++ -- DisplayEmail changes by Users must be confirmed, see 480 below
|
||||||
[ UserPostLastUpdate =. Just now | userPostAddress /= stgPostAddress ] ++
|
[ UserPostLastUpdate =. Just now | userPostAddress /= stgPostAddress ] ++
|
||||||
[ UserDisplayName =. stgDisplayName
|
[ UserDisplayName =. stgDisplayName
|
||||||
|
, UserScheduleView =. stgScheduleView
|
||||||
|
, UserScheduleWeekDays =. stgScheduleWeekDays
|
||||||
|
, UserScheduleWeekTimeFrom =. stgScheduleWeekTimeFrom
|
||||||
|
, UserScheduleWeekTimeTo =. stgScheduleWeekTimeTo
|
||||||
|
, UserScheduleWeekTimeslotLength =. stgScheduleWeekTimeslotLength
|
||||||
|
, UserScheduleOccurrenceDisplayDefault =. stgScheduleOccurrenceDisplayDefault
|
||||||
, UserMaxFavourites =. stgMaxFavourites
|
, UserMaxFavourites =. stgMaxFavourites
|
||||||
, UserMaxFavouriteTerms =. stgMaxFavouriteTerms
|
, UserMaxFavouriteTerms =. stgMaxFavouriteTerms
|
||||||
, UserTheme =. stgTheme
|
, UserTheme =. stgTheme
|
||||||
, UserDateTimeFormat =. stgDateTime
|
, UserDateTimeFormat =. stgDateTime
|
||||||
, UserDateFormat =. stgDate
|
, UserDateFormat =. stgDate
|
||||||
, UserTimeFormat =. stgTime
|
, UserTimeFormat =. stgTime
|
||||||
|
, UserWeekStart =. stgWeekStart
|
||||||
, UserDownloadFiles =. stgDownloadFiles
|
, UserDownloadFiles =. stgDownloadFiles
|
||||||
, UserWarningDays =. stgWarningDays
|
, UserWarningDays =. stgWarningDays
|
||||||
, UserNotificationSettings =. stgNotificationSettings
|
, UserNotificationSettings =. stgNotificationSettings
|
||||||
|
|||||||
105
src/Handler/Schedule.hs
Normal file
105
src/Handler/Schedule.hs
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
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,5 +13,6 @@ import Handler.Tutorial.Form as Handler.Tutorial
|
|||||||
import Handler.Tutorial.List as Handler.Tutorial
|
import Handler.Tutorial.List as Handler.Tutorial
|
||||||
import Handler.Tutorial.New as Handler.Tutorial
|
import Handler.Tutorial.New as Handler.Tutorial
|
||||||
import Handler.Tutorial.Register 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.TutorInvite as Handler.Tutorial
|
||||||
import Handler.Tutorial.Users as Handler.Tutorial
|
import Handler.Tutorial.Users as Handler.Tutorial
|
||||||
|
|||||||
40
src/Handler/Tutorial/Schedule.hs
Normal file
40
src/Handler/Tutorial/Schedule.hs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
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
|
||||||
@ -183,6 +183,16 @@ instance Button UniWorX ButtonSubmitDelete where
|
|||||||
nullaryPathPiece ''ButtonSubmitDelete $ camelToPathPiece' 1 . dropSuffix "'"
|
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.)
|
-- | Looks like a button, but is just a link (e.g. for create course, etc.)
|
||||||
linkButton :: Widget -- ^ Widget to display if unauthorized
|
linkButton :: Widget -- ^ Widget to display if unauthorized
|
||||||
-> Widget -- ^ Button label
|
-> Widget -- ^ Button label
|
||||||
@ -1389,7 +1399,7 @@ dayTimeField fs mutc = do
|
|||||||
|
|
||||||
fieldTimeFormat :: String
|
fieldTimeFormat :: String
|
||||||
-- fieldTimeFormat = "%e.%m.%y %k:%M"
|
-- fieldTimeFormat = "%e.%m.%y %k:%M"
|
||||||
fieldTimeFormat = "%Y-%m-%dT%H:%M:%S"
|
fieldTimeFormat = "%Y-%m-%dT%H:%M:%S%Q"
|
||||||
|
|
||||||
localTimeField :: (MonadHandler m, HandlerSite m ~ UniWorX) => Field m LocalTime
|
localTimeField :: (MonadHandler m, HandlerSite m ~ UniWorX) => Field m LocalTime
|
||||||
localTimeField = Field
|
localTimeField = Field
|
||||||
|
|||||||
@ -48,7 +48,7 @@ occurrencesAForm (toPathPiece -> miIdent') mPrev = wFormToAForm $ do
|
|||||||
(\p -> Just . SomeRoute $ cRoute :#: p)
|
(\p -> Just . SomeRoute $ cRoute :#: p)
|
||||||
miLayout'
|
miLayout'
|
||||||
(miIdent' <> "__scheduled" :: Text)
|
(miIdent' <> "__scheduled" :: Text)
|
||||||
(fslI MsgScheduleRegular)
|
(fslI MsgScheduleRegular & setTooltip MsgScheduleRegularTip)
|
||||||
False
|
False
|
||||||
(Set.toList . occurrencesScheduled <$> mPrev)
|
(Set.toList . occurrencesScheduled <$> mPrev)
|
||||||
where
|
where
|
||||||
|
|||||||
@ -25,6 +25,8 @@ import Settings.Cluster (ClusterSettingsKey)
|
|||||||
|
|
||||||
import Text.Blaze (ToMarkup(..))
|
import Text.Blaze (ToMarkup(..))
|
||||||
|
|
||||||
|
import Utils.Schedule.Types.ScheduleView
|
||||||
|
|
||||||
import Database.Persist.Sql (BackendKey(..))
|
import Database.Persist.Sql (BackendKey(..))
|
||||||
|
|
||||||
import qualified Database.Esqueleto.Legacy as E
|
import qualified Database.Esqueleto.Legacy as E
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
-- 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-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-License-Identifier: AGPL-3.0-or-later
|
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
@ -47,14 +47,18 @@ type Companies = [CI Text]
|
|||||||
|
|
||||||
type CourseName = CI Text
|
type CourseName = CI Text
|
||||||
type CourseShorthand = CI Text
|
type CourseShorthand = CI Text
|
||||||
|
type CourseEventType = CI Text
|
||||||
|
type CourseEventRoom = Text
|
||||||
type MaterialName = CI Text
|
type MaterialName = CI Text
|
||||||
type TutorialName = CI Text
|
type TutorialName = CI Text
|
||||||
|
type TutorialType = CI Text
|
||||||
type SheetName = CI Text
|
type SheetName = CI Text
|
||||||
type SubmissionGroupName = CI Text
|
type SubmissionGroupName = CI Text
|
||||||
|
|
||||||
type ExamName = CI Text
|
type ExamName = CI Text
|
||||||
type ExamPartName = CI Text
|
type ExamPartName = CI Text
|
||||||
type ExamOccurrenceName = CI Text
|
type ExamOccurrenceName = CI Text
|
||||||
|
type ExamOccurrenceRoom = Text
|
||||||
|
|
||||||
type PWHashAlgorithm = ByteString -> PWStore.Salt -> Int -> ByteString
|
type PWHashAlgorithm = ByteString -> PWStore.Salt -> Int -> ByteString
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
-- 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-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-License-Identifier: AGPL-3.0-or-later
|
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
@ -34,6 +34,8 @@ import Web.HttpApiData
|
|||||||
|
|
||||||
import Data.Aeson.Types as Aeson
|
import Data.Aeson.Types as Aeson
|
||||||
|
|
||||||
|
import Model.Types.TH.PathPiece
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
-- Terms and anything loosely related to time
|
-- Terms and anything loosely related to time
|
||||||
@ -227,6 +229,18 @@ derivePersistFieldJSON ''Occurrences
|
|||||||
|
|
||||||
|
|
||||||
nullaryPathPiece ''DayOfWeek camelToPathPiece
|
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]
|
-- test :: IO [OccurrenceException]
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
-- 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-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-License-Identifier: AGPL-3.0-or-later
|
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
@ -79,6 +79,8 @@ import qualified Network.Minio as Minio
|
|||||||
|
|
||||||
import Data.Conduit.Algorithms.FastCDC
|
import Data.Conduit.Algorithms.FastCDC
|
||||||
|
|
||||||
|
import Utils.Schedule.Types.ScheduleView
|
||||||
|
|
||||||
import Utils.Lens.TH
|
import Utils.Lens.TH
|
||||||
|
|
||||||
import qualified Data.Set as Set
|
import qualified Data.Set as Set
|
||||||
@ -282,9 +284,15 @@ data UserDefaultConf = UserDefaultConf
|
|||||||
{ userDefaultTheme :: Theme
|
{ userDefaultTheme :: Theme
|
||||||
, userDefaultMaxFavourites, userDefaultMaxFavouriteTerms :: Int
|
, userDefaultMaxFavourites, userDefaultMaxFavouriteTerms :: Int
|
||||||
, userDefaultDateTimeFormat, userDefaultDateFormat, userDefaultTimeFormat :: DateTimeFormat
|
, userDefaultDateTimeFormat, userDefaultDateFormat, userDefaultTimeFormat :: DateTimeFormat
|
||||||
|
, userDefaultWeekStart :: DayOfWeek
|
||||||
, userDefaultDownloadFiles :: Bool
|
, userDefaultDownloadFiles :: Bool
|
||||||
, userDefaultWarningDays :: NominalDiffTime
|
, userDefaultWarningDays :: NominalDiffTime
|
||||||
, userDefaultShowSex :: Bool
|
, userDefaultShowSex :: Bool
|
||||||
|
, userDefaultScheduleView :: ScheduleView
|
||||||
|
, userDefaultScheduleWeekDays :: ScheduleWeekDays
|
||||||
|
, userDefaultScheduleWeekTimeFrom, userDefaultScheduleWeekTimeTo :: NominalDiffTime
|
||||||
|
, userDefaultScheduleWeekTimeslotLength :: NominalDiffTime
|
||||||
|
, userDefaultScheduleOccurrenceDisplayDefault :: Bool
|
||||||
, userDefaultExamOfficeGetSynced :: Bool
|
, userDefaultExamOfficeGetSynced :: Bool
|
||||||
, userDefaultExamOfficeGetLabels :: Bool
|
, userDefaultExamOfficeGetLabels :: Bool
|
||||||
, userDefaultPrefersPostal :: Bool
|
, userDefaultPrefersPostal :: Bool
|
||||||
|
|||||||
15
src/Utils.hs
15
src/Utils.hs
@ -831,6 +831,21 @@ listBracket b@(s,e) (h:t)
|
|||||||
----------
|
----------
|
||||||
-- all functions that used to be here are now in Utils.Set
|
-- 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 --
|
-- Maps --
|
||||||
----------
|
----------
|
||||||
|
|||||||
@ -16,6 +16,7 @@ module Utils.DateTime
|
|||||||
, mkDateTimeFormatter
|
, mkDateTimeFormatter
|
||||||
, nominalHour, nominalMinute
|
, nominalHour, nominalMinute
|
||||||
, minNominalYear, avgNominalYear
|
, minNominalYear, avgNominalYear
|
||||||
|
, nominalTimeToTimeOfDay, timeOfDayToNominalTime
|
||||||
, diffMinute, diffHour, diffDay
|
, diffMinute, diffHour, diffDay
|
||||||
, module Zones
|
, module Zones
|
||||||
, day
|
, day
|
||||||
@ -33,6 +34,7 @@ import Data.Time.Zones (localTimeToUTCTZ, timeZoneForUTCTime)
|
|||||||
import Data.Time.Format (FormatTime)
|
import Data.Time.Format (FormatTime)
|
||||||
import Data.Time.Format.Instances ()
|
import Data.Time.Format.Instances ()
|
||||||
import Data.Time.Clock.System (systemEpochDay)
|
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.ISO8601 as Time
|
||||||
import qualified Data.Time.Format as Time
|
import qualified Data.Time.Format as Time
|
||||||
-- import Data.Time.Calendar (CalendarDiffDays, calendarMonth, scaleCalendarDiffDays)
|
-- import Data.Time.Calendar (CalendarDiffDays, calendarMonth, scaleCalendarDiffDays)
|
||||||
@ -162,6 +164,11 @@ minNominalYear, avgNominalYear :: NominalDiffTime
|
|||||||
minNominalYear = 365 * nominalDay
|
minNominalYear = 365 * nominalDay
|
||||||
avgNominalYear = fromRational $ 365.2425 * toRational nominalDay
|
avgNominalYear = fromRational $ 365.2425 * toRational nominalDay
|
||||||
|
|
||||||
|
nominalTimeToTimeOfDay :: NominalDiffTime -> TimeOfDay
|
||||||
|
nominalTimeToTimeOfDay = timeToTimeOfDay . realToFrac
|
||||||
|
timeOfDayToNominalTime :: TimeOfDay -> NominalDiffTime
|
||||||
|
timeOfDayToNominalTime = realToFrac . timeOfDayToTime
|
||||||
|
|
||||||
--------------
|
--------------
|
||||||
-- DiffTime --
|
-- DiffTime --
|
||||||
--------------
|
--------------
|
||||||
|
|||||||
@ -425,6 +425,27 @@ buttonField btn = Field{..}
|
|||||||
| otherwise = return . Left $ SomeMessage MsgWrongButtonValue
|
| otherwise = return . Left $ SomeMessage MsgWrongButtonValue
|
||||||
fieldParse _ _ = return . Left $ SomeMessage MsgMultipleButtonValues
|
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.
|
combinedButtonField :: forall a m.
|
||||||
( Button (HandlerSite m) a
|
( Button (HandlerSite m) a
|
||||||
, MonadHandler m
|
, MonadHandler m
|
||||||
@ -811,6 +832,28 @@ daysField = convertField fromDays toDays fractionalField
|
|||||||
toDays = (/ nominalDay)
|
toDays = (/ nominalDay)
|
||||||
fromDays = (* 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
|
data SecretJSONFieldException = SecretJSONFieldDecryptFailure
|
||||||
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic)
|
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic)
|
||||||
@ -1501,7 +1544,7 @@ hoistField f Field{..} = Field
|
|||||||
}
|
}
|
||||||
|
|
||||||
prismAForm :: Monad m => Prism' s a -> Maybe s -> (Maybe a -> AForm m a) -> AForm m s
|
prismAForm :: Monad m => Prism' s a -> Maybe s -> (Maybe a -> AForm m a) -> AForm m s
|
||||||
-- ^ @Monad m => Prism' s a -> (Maybe a -> AForm m a) -> (Maybe s -> AForm m s)@
|
-- ^ TODO: @Monad m => Prism' s a -> (Maybe a -> AForm m a) -> (Maybe s -> AForm m s)@
|
||||||
prismAForm p outer form = review p <$> form inner
|
prismAForm p outer form = review p <$> form inner
|
||||||
where
|
where
|
||||||
inner = outer >>= preview p
|
inner = outer >>= preview p
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
-- 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-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-License-Identifier: AGPL-3.0-or-later
|
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
@ -96,6 +96,11 @@ data Icon
|
|||||||
| IconFileUploadSession
|
| IconFileUploadSession
|
||||||
| IconStandaloneFieldError
|
| IconStandaloneFieldError
|
||||||
| IconFileUser
|
| IconFileUser
|
||||||
|
| IconFastBackward
|
||||||
|
| IconBackward
|
||||||
|
| IconCurrent
|
||||||
|
| IconForward
|
||||||
|
| IconFastForward
|
||||||
| IconPersonalIdentification
|
| IconPersonalIdentification
|
||||||
| IconMenuWorkflows
|
| IconMenuWorkflows
|
||||||
| IconVideo
|
| IconVideo
|
||||||
@ -194,6 +199,11 @@ iconText = \case
|
|||||||
IconFileUploadSession -> "file-upload"
|
IconFileUploadSession -> "file-upload"
|
||||||
IconStandaloneFieldError -> "exclamation"
|
IconStandaloneFieldError -> "exclamation"
|
||||||
IconFileUser -> "file-user"
|
IconFileUser -> "file-user"
|
||||||
|
IconFastBackward -> "angle-double-left"
|
||||||
|
IconBackward -> "angle-left"
|
||||||
|
IconCurrent -> "circle"
|
||||||
|
IconForward -> "angle-right"
|
||||||
|
IconFastForward -> "angle-double-right"
|
||||||
IconNotification -> "envelope"
|
IconNotification -> "envelope"
|
||||||
IconNotificationSent -> "envelope-open" -- "paper-plane", "shipping-fast", "hourglass-half"
|
IconNotificationSent -> "envelope-open" -- "paper-plane", "shipping-fast", "hourglass-half"
|
||||||
IconNoNotification -> "bell-slash"
|
IconNoNotification -> "bell-slash"
|
||||||
|
|||||||
@ -37,6 +37,7 @@ data GlobalGetParam = GetLang
|
|||||||
| GetError
|
| GetError
|
||||||
| GetSelectTable
|
| GetSelectTable
|
||||||
| GetGenerateToken
|
| GetGenerateToken
|
||||||
|
| GetScheduleOptions
|
||||||
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic)
|
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic)
|
||||||
deriving anyclass (Universe, Finite)
|
deriving anyclass (Universe, Finite)
|
||||||
|
|
||||||
|
|||||||
128
src/Utils/Schedule.hs
Normal file
128
src/Utils/Schedule.hs
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
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
|
||||||
37
src/Utils/Schedule/Types.hs
Normal file
37
src/Utils/Schedule/Types.hs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
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
|
||||||
|
)
|
||||||
|
)
|
||||||
34
src/Utils/Schedule/Types/ScheduleEntry.hs
Normal file
34
src/Utils/Schedule/Types/ScheduleEntry.hs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
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)
|
||||||
25
src/Utils/Schedule/Types/ScheduleOffset.hs
Normal file
25
src/Utils/Schedule/Types/ScheduleOffset.hs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
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
|
||||||
45
src/Utils/Schedule/Types/ScheduleOptions.hs
Normal file
45
src/Utils/Schedule/Types/ScheduleOptions.hs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
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
|
||||||
23
src/Utils/Schedule/Types/ScheduleView.hs
Normal file
23
src/Utils/Schedule/Types/ScheduleView.hs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
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
|
||||||
261
src/Utils/Schedule/Week.hs
Normal file
261
src/Utils/Schedule/Week.hs
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
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..]
|
||||||
34
src/Utils/Schedule/Week/SlotAssociation.hs
Normal file
34
src/Utils/Schedule/Week/SlotAssociation.hs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
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
|
||||||
67
src/Utils/Schedule/Week/TimeSlot.hs
Normal file
67
src/Utils/Schedule/Week/TimeSlot.hs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
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)
|
||||||
5
src/Utils/Schedule/Week/Types.hs
Normal file
5
src/Utils/Schedule/Week/Types.hs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module Utils.Schedule.Week.Types
|
||||||
|
( module Utils.Schedule.Week.Types
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Utils.Schedule.Week.Types.TimeSlot as Utils.Schedule.Week.Types
|
||||||
14
src/Utils/Schedule/Week/Types/TimeSlot.hs
Normal file
14
src/Utils/Schedule/Week/Types/TimeSlot.hs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
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)
|
||||||
25
src/Utils/Tutorial.hs
Normal file
25
src/Utils/Tutorial.hs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
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,12 +243,16 @@ $# $if NTop (Just 0) < NTop (courseCapacity course)
|
|||||||
_{MsgCourseEventRoom}
|
_{MsgCourseEventRoom}
|
||||||
<th .table__th uw-hide-column-header="note" :hiddenEventNotes:.course-event-note--hidden>
|
<th .table__th uw-hide-column-header="note" :hiddenEventNotes:.course-event-note--hidden>
|
||||||
_{MsgCourseEventNote}
|
_{MsgCourseEventNote}
|
||||||
|
$# TODO: merge with actions column
|
||||||
|
$if is _Just mbAuth
|
||||||
|
<th .table__th uw-hide-column-header="schedule-actions">
|
||||||
|
_{MsgScheduleOptActions}
|
||||||
$if mayCreateEvents
|
$if mayCreateEvents
|
||||||
<th .table__th uw-hide-column-header="actions">
|
<th .table__th uw-hide-column-header="actions">
|
||||||
_{MsgCourseEventActions}
|
_{MsgCourseEventActions}
|
||||||
\ #{iconInvisible}
|
\ #{iconInvisible}
|
||||||
<tbody>
|
<tbody>
|
||||||
$forall (cID, CourseEvent{courseEventType, courseEventTime, courseEventRoom, courseEventNote}, showRoom) <- events
|
$forall (cID, CourseEvent{courseEventType, courseEventTime, courseEventRoom, courseEventNote}, showRoom, courseEventCurrentOpt, mEventScheduleOpt) <- events
|
||||||
<tr .table__row ##{"event-" <> toPathPiece cID}>
|
<tr .table__row ##{"event-" <> toPathPiece cID}>
|
||||||
<td .table__td>
|
<td .table__td>
|
||||||
<div .table__td-content>
|
<div .table__td-content>
|
||||||
@ -269,6 +273,15 @@ $# $if NTop (Just 0) < NTop (courseCapacity course)
|
|||||||
<td .table__td :hiddenEventNotes:.course-event-note--hidden>
|
<td .table__td :hiddenEventNotes:.course-event-note--hidden>
|
||||||
<div .table__td-content>
|
<div .table__td-content>
|
||||||
#{courseEventNote}
|
#{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
|
$if mayCreateEvents
|
||||||
<td .table__td>
|
<td .table__td>
|
||||||
<ul .list--inline .list--iconless .list--comma-separated>
|
<ul .list--inline .list--iconless .list--comma-separated>
|
||||||
@ -293,3 +306,12 @@ $# $if NTop (Just 0) < NTop (courseCapacity course)
|
|||||||
<dd .deflist__dd>
|
<dd .deflist__dd>
|
||||||
^{tutorialTable}
|
^{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
|
$if not occurrenceAssignmentsVisible
|
||||||
\ ^{isVisible False}
|
\ ^{isVisible False}
|
||||||
$of _
|
$of _
|
||||||
<th .table__td>
|
<th .table__th>
|
||||||
$if not occurrenceAssignmentsVisible
|
$if not occurrenceAssignmentsVisible
|
||||||
^{isVisible False}
|
^{isVisible False}
|
||||||
$if showRegisteredCount
|
$if showRegisteredCount
|
||||||
@ -232,8 +232,11 @@ $if not (null occurrences)
|
|||||||
_{MsgExamRegisteredCount}
|
_{MsgExamRegisteredCount}
|
||||||
\ ^{isVisible False}
|
\ ^{isVisible False}
|
||||||
<th .table__th>_{MsgExamRoomDescription}
|
<th .table__th>_{MsgExamRoomDescription}
|
||||||
|
$if is _Just mAuth
|
||||||
|
<th .table__th>
|
||||||
|
_{MsgSchedule}
|
||||||
<tbody>
|
<tbody>
|
||||||
$forall (occurrence, registered, rCount, showRoom) <- occurrences
|
$forall (occurrence, registered, rCount, showRoom, shouldBeDisplayedInSchedule, mEOScheduleOpt) <- occurrences
|
||||||
$with Entity _occId ExamOccurrence{examOccurrenceName, examOccurrenceRoom, examOccurrenceStart, examOccurrenceEnd, examOccurrenceDescription} <- occurrence
|
$with Entity _occId ExamOccurrence{examOccurrenceName, examOccurrenceRoom, examOccurrenceStart, examOccurrenceEnd, examOccurrenceDescription} <- occurrence
|
||||||
$with registerWdgt <- registerWidget (Just occurrence)
|
$with registerWdgt <- registerWidget (Just occurrence)
|
||||||
<tr .table__row :markUnregisteredOccurrences (Just occurrence) && not registered:.occurrence--not-registered>
|
<tr .table__row :markUnregisteredOccurrences (Just occurrence) && not registered:.occurrence--not-registered>
|
||||||
@ -268,6 +271,14 @@ $if not (null occurrences)
|
|||||||
<td .table__td>
|
<td .table__td>
|
||||||
$maybe desc <- examOccurrenceDescription
|
$maybe desc <- examOccurrenceDescription
|
||||||
#{desc}
|
#{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>
|
<tfoot>
|
||||||
<tr .table__row .table__row--sum>
|
<tr .table__row .table__row--sum>
|
||||||
$if occurrenceNamesShown
|
$if occurrenceNamesShown
|
||||||
|
|||||||
17
templates/i18n/schedule-explanation/de-de-formal.hamlet
Normal file
17
templates/i18n/schedule-explanation/de-de-formal.hamlet
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
$# 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".
|
||||||
18
templates/i18n/schedule-explanation/en-eu.hamlet
Normal file
18
templates/i18n/schedule-explanation/en-eu.hamlet
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
$# 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".
|
||||||
6
templates/news/schedule.hamlet
Normal file
6
templates/news/schedule.hamlet
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
$newline never
|
||||||
|
<section>
|
||||||
|
<h2>
|
||||||
|
<a href=@{ScheduleR}>
|
||||||
|
_{MsgSchedule}
|
||||||
|
^{schedule}
|
||||||
10
templates/schedule.hamlet
Normal file
10
templates/schedule.hamlet
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
$newline never
|
||||||
|
|
||||||
|
<section>
|
||||||
|
^{schedule}
|
||||||
|
|
||||||
|
<form enctype=#{optionsEnctype} .schedule-options>
|
||||||
|
^{optionsWidget}
|
||||||
|
|
||||||
|
<section .explanation>
|
||||||
|
^{scheduleExplanation}
|
||||||
10
templates/schedule/options.hamlet
Normal file
10
templates/schedule/options.hamlet
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
$newline never
|
||||||
|
|
||||||
|
#{csrf}
|
||||||
|
|
||||||
|
$if length viewWidgets > 1
|
||||||
|
$forall vWgt <- viewWidgets
|
||||||
|
^{fvWidget vWgt}
|
||||||
|
|
||||||
|
$forall oWgt <- offsetWidgets
|
||||||
|
^{fvWidget oWgt}
|
||||||
77
templates/schedule/week.hamlet
Normal file
77
templates/schedule/week.hamlet
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
$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}
|
||||||
@ -1,7 +0,0 @@
|
|||||||
$newline never
|
|
||||||
|
|
||||||
$# SPDX-FileCopyrightText: 2022 Sarah Vaupel <vaupel.sarah@campus.lmu.de>
|
|
||||||
$#
|
|
||||||
$# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
|
|
||||||
_{MsgExceptionKindNoOccur}: #{exceptTime'}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
$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-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-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-License-Identifier: AGPL-3.0-or-later
|
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
@ -98,6 +98,7 @@ fillDb = do
|
|||||||
, userDateTimeFormat = userDefaultDateTimeFormat
|
, userDateTimeFormat = userDefaultDateTimeFormat
|
||||||
, userDateFormat = userDefaultDateFormat
|
, userDateFormat = userDefaultDateFormat
|
||||||
, userTimeFormat = userDefaultTimeFormat
|
, userTimeFormat = userDefaultTimeFormat
|
||||||
|
, userWeekStart = userDefaultWeekStart
|
||||||
, userDownloadFiles = userDefaultDownloadFiles
|
, userDownloadFiles = userDefaultDownloadFiles
|
||||||
, userWarningDays = userDefaultWarningDays
|
, userWarningDays = userDefaultWarningDays
|
||||||
, userLanguages = Just $ Languages ["en"]
|
, userLanguages = Just $ Languages ["en"]
|
||||||
@ -109,6 +110,12 @@ fillDb = do
|
|||||||
, userSex = Just SexMale
|
, userSex = Just SexMale
|
||||||
, userBirthday = Nothing
|
, userBirthday = Nothing
|
||||||
, userShowSex = userDefaultShowSex
|
, userShowSex = userDefaultShowSex
|
||||||
|
, userScheduleView = userDefaultScheduleView
|
||||||
|
, userScheduleWeekDays = userDefaultScheduleWeekDays
|
||||||
|
, userScheduleWeekTimeFrom = userDefaultScheduleWeekTimeFrom
|
||||||
|
, userScheduleWeekTimeTo = userDefaultScheduleWeekTimeTo
|
||||||
|
, userScheduleWeekTimeslotLength = userDefaultScheduleWeekTimeslotLength
|
||||||
|
, userScheduleOccurrenceDisplayDefault = userDefaultScheduleOccurrenceDisplayDefault
|
||||||
, userTelephone = Nothing
|
, userTelephone = Nothing
|
||||||
, userMobile = Nothing
|
, userMobile = Nothing
|
||||||
, userCompanyPersonalNumber = Just "00000"
|
, userCompanyPersonalNumber = Just "00000"
|
||||||
@ -138,6 +145,7 @@ fillDb = do
|
|||||||
, userDateTimeFormat = userDefaultDateTimeFormat
|
, userDateTimeFormat = userDefaultDateTimeFormat
|
||||||
, userDateFormat = userDefaultDateFormat
|
, userDateFormat = userDefaultDateFormat
|
||||||
, userTimeFormat = userDefaultTimeFormat
|
, userTimeFormat = userDefaultTimeFormat
|
||||||
|
, userWeekStart = userDefaultWeekStart
|
||||||
, userDownloadFiles = userDefaultDownloadFiles
|
, userDownloadFiles = userDefaultDownloadFiles
|
||||||
, userWarningDays = userDefaultWarningDays
|
, userWarningDays = userDefaultWarningDays
|
||||||
, userLanguages = Nothing
|
, userLanguages = Nothing
|
||||||
@ -148,6 +156,12 @@ fillDb = do
|
|||||||
, userCsvOptions = def { csvFormat = csvPreset # CsvPresetExcel }
|
, userCsvOptions = def { csvFormat = csvPreset # CsvPresetExcel }
|
||||||
, userSex = Just SexMale
|
, userSex = Just SexMale
|
||||||
, userShowSex = userDefaultShowSex
|
, userShowSex = userDefaultShowSex
|
||||||
|
, userScheduleView = userDefaultScheduleView
|
||||||
|
, userScheduleWeekDays = userDefaultScheduleWeekDays
|
||||||
|
, userScheduleWeekTimeFrom = userDefaultScheduleWeekTimeFrom
|
||||||
|
, userScheduleWeekTimeTo = userDefaultScheduleWeekTimeTo
|
||||||
|
, userScheduleWeekTimeslotLength = userDefaultScheduleWeekTimeslotLength
|
||||||
|
, userScheduleOccurrenceDisplayDefault = userDefaultScheduleOccurrenceDisplayDefault
|
||||||
, userBirthday = Nothing
|
, userBirthday = Nothing
|
||||||
, userMobile = Nothing
|
, userMobile = Nothing
|
||||||
, userTelephone = Nothing
|
, userTelephone = Nothing
|
||||||
@ -184,6 +198,7 @@ fillDb = do
|
|||||||
, userDateTimeFormat = userDefaultDateTimeFormat
|
, userDateTimeFormat = userDefaultDateTimeFormat
|
||||||
, userDateFormat = userDefaultDateFormat
|
, userDateFormat = userDefaultDateFormat
|
||||||
, userTimeFormat = userDefaultTimeFormat
|
, userTimeFormat = userDefaultTimeFormat
|
||||||
|
, userWeekStart = userDefaultWeekStart
|
||||||
, userDownloadFiles = userDefaultDownloadFiles
|
, userDownloadFiles = userDefaultDownloadFiles
|
||||||
, userWarningDays = userDefaultWarningDays
|
, userWarningDays = userDefaultWarningDays
|
||||||
, userLanguages = Nothing
|
, userLanguages = Nothing
|
||||||
@ -195,6 +210,12 @@ fillDb = do
|
|||||||
, userBirthday = Just $ n_day $ 35 * (-365)
|
, userBirthday = Just $ n_day $ 35 * (-365)
|
||||||
, userCsvOptions = def
|
, userCsvOptions = def
|
||||||
, userShowSex = userDefaultShowSex
|
, userShowSex = userDefaultShowSex
|
||||||
|
, userScheduleView = userDefaultScheduleView
|
||||||
|
, userScheduleWeekDays = userDefaultScheduleWeekDays
|
||||||
|
, userScheduleWeekTimeFrom = userDefaultScheduleWeekTimeFrom
|
||||||
|
, userScheduleWeekTimeTo = userDefaultScheduleWeekTimeTo
|
||||||
|
, userScheduleWeekTimeslotLength = userDefaultScheduleWeekTimeslotLength
|
||||||
|
, userScheduleOccurrenceDisplayDefault = userDefaultScheduleOccurrenceDisplayDefault
|
||||||
, userTelephone = Just "+49 69 690-71706"
|
, userTelephone = Just "+49 69 690-71706"
|
||||||
, userMobile = Just "0173 69 99 646"
|
, userMobile = Just "0173 69 99 646"
|
||||||
, userCompanyPersonalNumber = Just "57138"
|
, userCompanyPersonalNumber = Just "57138"
|
||||||
@ -224,6 +245,7 @@ fillDb = do
|
|||||||
, userMaxFavouriteTerms = userDefaultMaxFavouriteTerms
|
, userMaxFavouriteTerms = userDefaultMaxFavouriteTerms
|
||||||
, userDateFormat = userDefaultDateFormat
|
, userDateFormat = userDefaultDateFormat
|
||||||
, userTimeFormat = userDefaultTimeFormat
|
, userTimeFormat = userDefaultTimeFormat
|
||||||
|
, userWeekStart = userDefaultWeekStart
|
||||||
, userDownloadFiles = userDefaultDownloadFiles
|
, userDownloadFiles = userDefaultDownloadFiles
|
||||||
, userWarningDays = userDefaultWarningDays
|
, userWarningDays = userDefaultWarningDays
|
||||||
, userLanguages = Just $ Languages ["de"]
|
, userLanguages = Just $ Languages ["de"]
|
||||||
@ -235,6 +257,12 @@ fillDb = do
|
|||||||
, userSex = Just SexMale
|
, userSex = Just SexMale
|
||||||
, userBirthday = Just $ n_day $ 27 * (-365)
|
, userBirthday = Just $ n_day $ 27 * (-365)
|
||||||
, userShowSex = userDefaultShowSex
|
, userShowSex = userDefaultShowSex
|
||||||
|
, userScheduleView = userDefaultScheduleView
|
||||||
|
, userScheduleWeekDays = userDefaultScheduleWeekDays
|
||||||
|
, userScheduleWeekTimeFrom = userDefaultScheduleWeekTimeFrom
|
||||||
|
, userScheduleWeekTimeTo = userDefaultScheduleWeekTimeTo
|
||||||
|
, userScheduleWeekTimeslotLength = userDefaultScheduleWeekTimeslotLength
|
||||||
|
, userScheduleOccurrenceDisplayDefault = userDefaultScheduleOccurrenceDisplayDefault
|
||||||
, userTelephone = Nothing
|
, userTelephone = Nothing
|
||||||
, userMobile = Nothing
|
, userMobile = Nothing
|
||||||
, userCompanyPersonalNumber = Nothing
|
, userCompanyPersonalNumber = Nothing
|
||||||
@ -264,6 +292,7 @@ fillDb = do
|
|||||||
, userDateTimeFormat = userDefaultDateTimeFormat
|
, userDateTimeFormat = userDefaultDateTimeFormat
|
||||||
, userDateFormat = userDefaultDateFormat
|
, userDateFormat = userDefaultDateFormat
|
||||||
, userTimeFormat = userDefaultTimeFormat
|
, userTimeFormat = userDefaultTimeFormat
|
||||||
|
, userWeekStart = userDefaultWeekStart
|
||||||
, userDownloadFiles = userDefaultDownloadFiles
|
, userDownloadFiles = userDefaultDownloadFiles
|
||||||
, userWarningDays = userDefaultWarningDays
|
, userWarningDays = userDefaultWarningDays
|
||||||
, userLanguages = Just $ Languages ["sn"]
|
, userLanguages = Just $ Languages ["sn"]
|
||||||
@ -275,6 +304,12 @@ fillDb = do
|
|||||||
, userSex = Just SexNotApplicable
|
, userSex = Just SexNotApplicable
|
||||||
, userBirthday = Just $ n_day 3
|
, userBirthday = Just $ n_day 3
|
||||||
, userShowSex = userDefaultShowSex
|
, userShowSex = userDefaultShowSex
|
||||||
|
, userScheduleView = userDefaultScheduleView
|
||||||
|
, userScheduleWeekDays = userDefaultScheduleWeekDays
|
||||||
|
, userScheduleWeekTimeFrom = userDefaultScheduleWeekTimeFrom
|
||||||
|
, userScheduleWeekTimeTo = userDefaultScheduleWeekTimeTo
|
||||||
|
, userScheduleWeekTimeslotLength = userDefaultScheduleWeekTimeslotLength
|
||||||
|
, userScheduleOccurrenceDisplayDefault = userDefaultScheduleOccurrenceDisplayDefault
|
||||||
, userTelephone = Nothing
|
, userTelephone = Nothing
|
||||||
, userMobile = Nothing
|
, userMobile = Nothing
|
||||||
, userCompanyPersonalNumber = Just "12345"
|
, userCompanyPersonalNumber = Just "12345"
|
||||||
@ -291,7 +326,7 @@ fillDb = do
|
|||||||
, userAuthentication = AuthLDAP
|
, userAuthentication = AuthLDAP
|
||||||
, userLastAuthentication = Nothing
|
, userLastAuthentication = Nothing
|
||||||
, userTokensIssuedAfter = Nothing
|
, userTokensIssuedAfter = Nothing
|
||||||
, userMatrikelnummer = Nothing
|
, userMatrikelnummer = Just "11323801"
|
||||||
, userEmail = "vaupel.sarah@campus.lmu.de"
|
, userEmail = "vaupel.sarah@campus.lmu.de"
|
||||||
, userDisplayEmail = "vaupel.sarah@campus.lmu.de"
|
, userDisplayEmail = "vaupel.sarah@campus.lmu.de"
|
||||||
, userDisplayName = "Sarah Vaupel"
|
, userDisplayName = "Sarah Vaupel"
|
||||||
@ -300,10 +335,11 @@ fillDb = do
|
|||||||
, userTitle = Nothing
|
, userTitle = Nothing
|
||||||
, userMaxFavourites = 14
|
, userMaxFavourites = 14
|
||||||
, userMaxFavouriteTerms = 4
|
, userMaxFavouriteTerms = 4
|
||||||
, userTheme = ThemeMossGreen
|
, userTheme = ThemeNeutralBlue
|
||||||
, userDateTimeFormat = userDefaultDateTimeFormat
|
, userDateTimeFormat = userDefaultDateTimeFormat
|
||||||
, userDateFormat = userDefaultDateFormat
|
, userDateFormat = userDefaultDateFormat
|
||||||
, userTimeFormat = userDefaultTimeFormat
|
, userTimeFormat = userDefaultTimeFormat
|
||||||
|
, userWeekStart = userDefaultWeekStart
|
||||||
, userDownloadFiles = userDefaultDownloadFiles
|
, userDownloadFiles = userDefaultDownloadFiles
|
||||||
, userWarningDays = userDefaultWarningDays
|
, userWarningDays = userDefaultWarningDays
|
||||||
, userLanguages = Nothing
|
, userLanguages = Nothing
|
||||||
@ -315,6 +351,12 @@ fillDb = do
|
|||||||
, userSex = Just SexFemale
|
, userSex = Just SexFemale
|
||||||
, userBirthday = Nothing
|
, userBirthday = Nothing
|
||||||
, userShowSex = userDefaultShowSex
|
, userShowSex = userDefaultShowSex
|
||||||
|
, userScheduleView = userDefaultScheduleView
|
||||||
|
, userScheduleWeekDays = userDefaultScheduleWeekDays
|
||||||
|
, userScheduleWeekTimeFrom = userDefaultScheduleWeekTimeFrom
|
||||||
|
, userScheduleWeekTimeTo = userDefaultScheduleWeekTimeTo
|
||||||
|
, userScheduleWeekTimeslotLength = userDefaultScheduleWeekTimeslotLength
|
||||||
|
, userScheduleOccurrenceDisplayDefault = userDefaultScheduleOccurrenceDisplayDefault
|
||||||
, userTelephone = Nothing
|
, userTelephone = Nothing
|
||||||
, userMobile = Nothing
|
, userMobile = Nothing
|
||||||
, userCompanyPersonalNumber = Nothing
|
, userCompanyPersonalNumber = Nothing
|
||||||
@ -344,6 +386,7 @@ fillDb = do
|
|||||||
, userDateTimeFormat = userDefaultDateTimeFormat
|
, userDateTimeFormat = userDefaultDateTimeFormat
|
||||||
, userDateFormat = userDefaultDateFormat
|
, userDateFormat = userDefaultDateFormat
|
||||||
, userTimeFormat = userDefaultTimeFormat
|
, userTimeFormat = userDefaultTimeFormat
|
||||||
|
, userWeekStart = userDefaultWeekStart
|
||||||
, userDownloadFiles = userDefaultDownloadFiles
|
, userDownloadFiles = userDefaultDownloadFiles
|
||||||
, userWarningDays = userDefaultWarningDays
|
, userWarningDays = userDefaultWarningDays
|
||||||
, userLanguages = Nothing
|
, userLanguages = Nothing
|
||||||
@ -355,6 +398,12 @@ fillDb = do
|
|||||||
, userSex = Just SexMale
|
, userSex = Just SexMale
|
||||||
, userBirthday = Nothing
|
, userBirthday = Nothing
|
||||||
, userShowSex = userDefaultShowSex
|
, userShowSex = userDefaultShowSex
|
||||||
|
, userScheduleView = userDefaultScheduleView
|
||||||
|
, userScheduleWeekDays = userDefaultScheduleWeekDays
|
||||||
|
, userScheduleWeekTimeFrom = userDefaultScheduleWeekTimeFrom
|
||||||
|
, userScheduleWeekTimeTo = userDefaultScheduleWeekTimeTo
|
||||||
|
, userScheduleWeekTimeslotLength = userDefaultScheduleWeekTimeslotLength
|
||||||
|
, userScheduleOccurrenceDisplayDefault = userDefaultScheduleOccurrenceDisplayDefault
|
||||||
, userTelephone = Nothing
|
, userTelephone = Nothing
|
||||||
, userMobile = Nothing
|
, userMobile = Nothing
|
||||||
, userCompanyPersonalNumber = Nothing
|
, userCompanyPersonalNumber = Nothing
|
||||||
@ -544,6 +593,7 @@ fillDb = do
|
|||||||
, userDateTimeFormat = userDefaultDateTimeFormat
|
, userDateTimeFormat = userDefaultDateTimeFormat
|
||||||
, userDateFormat = userDefaultDateFormat
|
, userDateFormat = userDefaultDateFormat
|
||||||
, userTimeFormat = userDefaultTimeFormat
|
, userTimeFormat = userDefaultTimeFormat
|
||||||
|
, userWeekStart = userDefaultWeekStart
|
||||||
, userDownloadFiles = userDefaultDownloadFiles
|
, userDownloadFiles = userDefaultDownloadFiles
|
||||||
, userWarningDays = userDefaultWarningDays
|
, userWarningDays = userDefaultWarningDays
|
||||||
, userLanguages = Nothing
|
, userLanguages = Nothing
|
||||||
@ -555,6 +605,12 @@ fillDb = do
|
|||||||
, userSex = Nothing
|
, userSex = Nothing
|
||||||
, userBirthday = Nothing
|
, userBirthday = Nothing
|
||||||
, userShowSex = userDefaultShowSex
|
, userShowSex = userDefaultShowSex
|
||||||
|
, userScheduleView = userDefaultScheduleView
|
||||||
|
, userScheduleWeekDays = userDefaultScheduleWeekDays
|
||||||
|
, userScheduleWeekTimeFrom = userDefaultScheduleWeekTimeFrom
|
||||||
|
, userScheduleWeekTimeTo = userDefaultScheduleWeekTimeTo
|
||||||
|
, userScheduleWeekTimeslotLength = userDefaultScheduleWeekTimeslotLength
|
||||||
|
, userScheduleOccurrenceDisplayDefault = userDefaultScheduleOccurrenceDisplayDefault
|
||||||
, userTelephone = Nothing
|
, userTelephone = Nothing
|
||||||
, userMobile = Nothing
|
, userMobile = Nothing
|
||||||
, userCompanyPersonalNumber = bool Nothing (Just "E123" ) (even $ length firstName)
|
, userCompanyPersonalNumber = bool Nothing (Just "E123" ) (even $ length firstName)
|
||||||
|
|||||||
Reference in New Issue
Block a user