fix(corrections-overview): behavioural fixes

This commit is contained in:
Gregor Kleen 2020-06-12 10:24:34 +02:00
parent 1c66f6320a
commit e10cfe9c58
6 changed files with 241 additions and 189 deletions

View File

@ -1223,7 +1223,7 @@ a.breadcrumbs__home
font-size: 14px
font-family: monospace
.func-field__wrapper, .allocation-missing-prios, .allocation-users__accept
.func-field__wrapper, .allocation-missing-prios, .allocation-users__accept, .corrections-overview__section
max-height: 75vh
overflow: auto

View File

@ -107,9 +107,10 @@
text-align: right
position: absolute
left: 0px
top: 0
bottom: 0
width: 50px
height: 100%
max-height: 100vh
z-index: 40
&::before
@ -130,9 +131,10 @@
text-align: right
position: absolute
right: 0px
top: 0
bottom: 0
width: 60px
height: 100%
max-height: 100vh
transition: all .3s ease
z-index: 40

View File

@ -32,6 +32,7 @@ BtnLecInvDecline: Ablehnen
BtnCorrInvAccept: Annehmen
BtnCorrInvDecline: Ablehnen
BtnSubmissionsAssign: Abgaben automatisch zuteilen
BtnSubmissionsAssignAll: Abgaben automatisch zuteilen
BtnAllocationCompute: Vergabe berechnen
BtnAllocationAccept: Vergabe akzeptieren
BtnSystemMessageHide: Verstecken
@ -633,6 +634,7 @@ NrSubmissionsNotCorrected: Unkorrigiert
NrSubmissionsNotCorrectedShort: Unkg.
CorrectionTime: Korrekturdauer
AssignSubmissionsRandomWarning: Die Zuteilungsvorschau kann von der tatsächlichen Zuteilung abweichen, wenn mehrere Blätter auf einmal zugeteilt werden, da beim Ausgleich der Kontigente nur bereits zugeteilte Abgaben berücksichtigt werden. Da es ein randomisierte Prozess ist, kann es auch bei einzelnen Blättern gerinfgügige Abweichungen geben.
AssignSubmissionsAssignableSheets: Korrekturen verteilen für:
CorrectionsUploaded num@Int64: #{num} Korrekturen wurden gespeichert:
NoCorrectionsUploaded: In der hochgeladenen Datei wurden keine Korrekturen gefunden.

View File

@ -32,6 +32,7 @@ BtnLecInvDecline: Decline
BtnCorrInvAccept: Accept
BtnCorrInvDecline: Decline
BtnSubmissionsAssign: Assign submissions automatically
BtnSubmissionsAssignAll: Automatically distribute corrections
BtnAllocationCompute: Compute allocation
BtnAllocationAccept: Accept allocation
BtnSystemMessageHide: Hide
@ -631,6 +632,7 @@ NrSubmissionsNotCorrected: Not corrected
NrSubmissionsNotCorrectedShort: N.corr.
CorrectionTime: Correction time
AssignSubmissionsRandomWarning: The assignment preview might be different from the actual assignment if multiple sheets are being distributed. This is due to the fact that only assigned submissions are considered when handling corrector-deficits. Due to this being a randomised process small differences are also possible for a single sheet.
AssignSubmissionsAssignableSheets: Distribute corrections for:
CorrectionsUploaded num: Successfully saved #{num} #{pluralEN num "correction" "corrections"}:
NoCorrectionsUploaded: No corrections could be found within the uploaded file.

View File

@ -4,7 +4,7 @@ module Handler.Submission.Assign
, getSAssignR, postSAssignR
) where
import Import hiding (link)
import Import hiding (link, unzip)
import Handler.Utils hiding (colSchool)
import Handler.Utils.Corrections
@ -12,7 +12,7 @@ import Handler.Utils.Submission
import Data.List as List (foldl, foldr)
import qualified Data.Set as Set
import Data.Map.Strict ((!))
import Data.Map.Strict ((!), (!?))
import qualified Data.Map.Strict as Map
import qualified Data.Text as Text
@ -20,6 +20,8 @@ import qualified Data.CaseInsensitive as CI
import qualified Database.Esqueleto as E
import Data.List.NonEmpty (unzip)
getSubAssignR, postSubAssignR :: TermId -> SchoolId -> CourseShorthand -> SheetName -> CryptoFileNameSubmission -> Handler Html
getSubAssignR = postSubAssignR
@ -56,15 +58,6 @@ postSubAssignR tid ssh csh shn cID = do
$(widgetFile "submission-assign")
data ButtonSubmissionsAssign = BtnSubmissionsAssign
deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
instance Universe ButtonSubmissionsAssign
instance Finite ButtonSubmissionsAssign
nullaryPathPiece ''ButtonSubmissionsAssign camelToPathPiece
embedRenderMessage ''UniWorX ''ButtonSubmissionsAssign id
instance Button UniWorX ButtonSubmissionsAssign where
btnClasses BtnSubmissionsAssign = [BCIsButton, BCPrimary]
getCAssignR, postCAssignR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
getCAssignR = postCAssignR
postCAssignR tid ssh csh = do
@ -77,35 +70,22 @@ postSAssignR tid ssh csh shn = do
(shid,cid) <- runDB $ fetchSheetIdCourseId tid ssh csh shn
assignHandler tid ssh csh cid [shid]
{- TODO: Feature:
make distivt buttons for each sheet, so that users see which sheet will be assigned.
Currently this information is available within the page heading!
Stub:
data ButtonCorrectionsAssign = BtnCorrectionsAssignAll | BtnCorrectionsAssignSheet SheetName
deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
instance Button UniWorX ButtonCorrectionsAssign
-- Are those needed any more?
instance Universe ButtonCorrectionsAssign
instance Finite ButtonCorrectionsAssign
nullaryPathPiece ''ButtonCorrectionsAssign camelToPathPiece
embedRenderMessage ''UniWorX ''ButtonCorrectionsAssign id
instance Button UniWorX ButtonCorrectionsAssign where
btnClasses BtnCorrectionsAssign = [BCIsButton, BCPrimary]
-- use runButtonForm' instead later on
-}
assignHandler :: TermId -> SchoolId -> CourseShorthand -> CourseId -> [SheetId] -> Handler Html
assignHandler tid ssh csh cid assignSids = do
-- evaluate form first, since it affects DB action
(btnWdgt, btnResult) <- runButtonForm FIDAssignSubmissions
currentRoute <- fromMaybe (error "assignHandler called from 404-handler") <$> liftHandler getCurrentRoute
-- gather data
(orderedSheetNames, assignSheetNames, nrParticipants, groupsPossible, infoMap, correctorMap, assignment) <- runDB $ do
(orderedSheetNames, assignSheetNames, nrParticipants, groupsPossible, infoMap, correctorMap, assignment, ((btnViews, btnCsrf), btnEncoding)) <- runDB $ do
-- cid <- getKeyBy404 $ TermSchoolCourseShort tid ssh csh
nrParticipants <- count [CourseParticipantCourse ==. cid, CourseParticipantState ==. CourseParticipantActive ]
sheetList <- selectList [SheetCourse ==. cid] [Desc SheetActiveTo, Desc SheetActiveFrom]
assignSids' <- if
| null assignSids -> -- assignAll; we distinguish assignSids' here avoid useless Alerts
selectKeysList [SheetCourse ==. cid] [Asc SheetActiveTo]
| otherwise -> return $ filter (`elem` map entityKey sheetList) assignSids
let orderedSheetNames = fmap (\(Entity _ Sheet{sheetName}) -> sheetName) sheetList
sheets = entities2map sheetList
sheetIds = Map.keys sheets
@ -114,6 +94,12 @@ assignHandler tid ssh csh cid assignSids = do
let foldFun (Entity _ Sheet{sheetGrouping=sgr}) acc = acc || sgr /= NoGroups
in List.foldr foldFun False sheetList
assignSheetNames = fmap sheetName $ mapMaybe (\sid -> Map.lookup sid sheets) assignSids
assignSheetNames' = fmap sheetName $ mapMaybe (\sid -> Map.lookup sid sheets) assignSids'
assignButtons = Map.fromSet (maybe BtnSubmissionsAssignAll BtnSubmissionsAssign) $ Set.fromList . bool (Nothing :) id (null sheetList) $ map Just assignSheetNames'
((btnResult, btnViews'), btnEncoding) <- runFormPost . identifyForm FIDAssignSubmissions $ \csrf ->
fmap (over _1 (asum . fmap (hoistMaybe =<<)) . over _2 (, csrf) . unzip) . for assignButtons $ \btn -> mopt (buttonField btn) "" Nothing
-- plan or assign unassigned submissions for given sheets
let buildA :: (Map SheetName ((Set SubmissionId, Set SubmissionId), Map (Maybe UserId) Int, Map UserId Rational)) -> SheetId -> DB (Map SheetName ((Set SubmissionId, Set SubmissionId), Map (Maybe UserId) Int, Map UserId Rational))
@ -128,24 +114,29 @@ assignHandler tid ssh csh cid assignSids = do
ignoreExceptions (SubmissionsNotFound _sids_not_found) = return mempty -- cannot happen, since last argument to planSubmissions is Nothing
(plan,deficit) <- lift $ handle ignoreExceptions $ planSubmissions sid Nothing
guard $ not $ null plan -- only proceed if there is a plan for this sheet
-- implement assignment plan
status <- lift $ case btnResult of
Nothing -> return (Set.empty, Set.empty)
(Just BtnSubmissionsAssign) -> do
status@(sub_ok,sub_fail) <- writeSubmissionPlan plan
let nr_ok = olength sub_ok
nr_fail = olength sub_fail
alert_ok = toMaybe (nr_ok > 0) $ SomeMessage $ MsgUpdatedSheetCorrectorsAutoAssigned nr_ok
alert_fail = toMaybe (nr_fail > 0) $ SomeMessage $ MsgUpdatedSheetCorrectorsAutoFailed nr_fail
msg_status = bool Success Error $ nr_fail > 0
msg_header = SomeMessage $ shn <> ":"
when (nr_ok > 0 || nr_fail > 0) $
addMessageI msg_status $ UniWorXMessages $ msg_header : catMaybes [alert_ok, alert_fail]
return status
return $ Map.insert shn (status, countMapElems plan, deficit) acc
assignSids' <- if null assignSids -- assignAll; we distinguish assignSids' here avoid useless Alerts
then selectKeysList [SheetCourse ==. cid] [Asc SheetActiveTo]
else return assignSids
status@(sub_ok, sub_fail) <- fmap fold . formResultMaybe btnResult $ \btn -> lift . maybeT (return Nothing) $ do
guard $ case btn of
BtnSubmissionsAssignAll -> True
BtnSubmissionsAssign shn' -> shn' == shn
status@(sub_ok,sub_fail) <- lift $ writeSubmissionPlan plan
let nr_ok = olength sub_ok
nr_fail = olength sub_fail
alert_ok = toMaybe (nr_ok > 0) $ SomeMessage $ MsgUpdatedSheetCorrectorsAutoAssigned nr_ok
alert_fail = toMaybe (nr_fail > 0) $ SomeMessage $ MsgUpdatedSheetCorrectorsAutoFailed nr_fail
msg_status = bool Success Error $ nr_fail > 0
msg_header = SomeMessage $ shn <> ":"
if | nr_ok > 0 || nr_fail > 0 -> do
addMessageI msg_status $ UniWorXMessages $ msg_header : catMaybes [alert_ok, alert_fail]
return $ Just status
| otherwise -> do
addMessageI Error $ MsgSheetsUnassignable $ CI.original shn
return Nothing
if | null sub_ok && null sub_fail ->
return $ Map.insert shn (status, countMapElems plan, deficit) acc
| otherwise -> do
(plan', deficit') <- lift $ handle ignoreExceptions $ planSubmissions sid Nothing
return $ Map.insert shn (status, countMapElems plan', deficit') acc
assignment <- foldM buildA Map.empty assignSids'
correctors <- E.select . E.from $ \(corrector `E.InnerJoin` user) -> do
@ -202,7 +193,7 @@ assignHandler tid ssh csh cid assignSids = do
}
in Map.insertWith (Map.unionWith (<>)) shnm cinf m
return (orderedSheetNames, assignSheetNames, nrParticipants, groupsPossible, infoMap, correctorMap, assignment)
return (orderedSheetNames, assignSheetNames, nrParticipants, groupsPossible, infoMap, correctorMap, assignment, (btnViews', btnEncoding))
let -- infoMap :: Map SheetName (Map (Maybe UserId) CorrectionInfo) -- repeated here for easier reference
-- create aggregate maps
@ -268,14 +259,39 @@ assignHandler tid ssh csh cid assignSids = do
showAvgsDays :: Maybe NominalDiffTime -> Integer -> Text
showAvgsDays Nothing _ = mempty
showAvgsDays (Just dt) n = formatDiffDays $ dt / fromIntegral n
let headingShort
| 0 < Map.size assignment = MsgMenuCorrectionsAssignSheet $ Text.intercalate ", " $ fmap CI.original $ Map.keys assignment
| otherwise = MsgMenuCorrectionsAssign
headingLong = prependCourseTitle tid ssh csh MsgMenuCorrectionsAssign
let headingShort = MsgMenuCorrectionsAssign
headingLong = prependCourseTitle tid ssh csh MsgMenuCorrectionsAssign
unassignableSheets = filter (\shn -> Map.notMember shn assignment) assignSheetNames
unless (null unassignableSheets) $ addMessageI Warning $ MsgSheetsUnassignable $ Text.intercalate ", " $ fmap CI.original unassignableSheets
siteLayoutMsg headingShort $ do
setTitleI headingLong
$(widgetFile "corrections-overview")
let doWrap wdgt
| null btnViews = wdgt
| otherwise = wrapForm (toWidget btnCsrf <> wdgt) def
{ formAction = Just . SomeRoute $ currentRoute
, formEncoding = btnEncoding
, formSubmit = FormNoSubmit
}
sheetBtnViews = Map.fromList [ (shn, btn) | (Just shn, btn) <- Map.toList btnViews, shn `elem` assignSheetNames' ]
assignSheetNames' = Map.keys $ Map.filter (\(_, new, _) -> any (> 0) $ Map.delete Nothing new) assignment
doWrap $(widgetFile "corrections-overview")
data ButtonSubmissionsAssign
= BtnSubmissionsAssign SheetName
| BtnSubmissionsAssignAll
deriving (Eq, Ord, Read, Show, Generic, Typeable)
derivePathPiece ''ButtonSubmissionsAssign (camelToPathPiece' 2) "--"
instance RenderMessage UniWorX ButtonSubmissionsAssign where
renderMessage f ls = \case
BtnSubmissionsAssign _ -> mr MsgBtnSubmissionsAssign
BtnSubmissionsAssignAll -> mr MsgBtnSubmissionsAssignAll
where mr = renderMessage f ls
instance Button UniWorX ButtonSubmissionsAssign where
btnClasses _ = [BCIsButton, BCPrimary]

View File

@ -2,146 +2,176 @@ $newline never
<section>
<h2>_{MsgCorrectionSheets}
_{MsgCourseParticipants nrParticipants}
<table .table .table--striped .table--hover>
<tr .table__row .table__row--head>
<th .table__th rowspan=2>_{MsgSheet}
$if groupsPossible
<th .table__th rowspan=2>_{MsgNrSubmittorsTotal}
<th .table__th rowspan=2>_{MsgNrSubmissionsTotal}
<th .table__th colspan=2>_{MsgNrSubmissionsNotAssigned}
<th .table__th rowspan=2>_{MsgNrSubmissionsNotCorrected}
<th .table__th colspan=3>_{MsgCorrectionTime}
<tr .table__row .table__row--head>
<th .table__th>
<th .table__th>_{MsgGenericNumChange}
<th .table__th>_{MsgGenericMin}
<th .table__th>_{MsgGenericAvg}
<th .table__th>_{MsgGenericMax}
$# Always iterate over orderedSheetNames for consistent sorting! Newest first, except in this table
$forall sheetName <- reverse orderedSheetNames
$maybe CorrectionInfo{ciSubmittors, ciSubmissions, ciAssigned, ciCorrected, ciMin, ciTot, ciMax} <- Map.lookup sheetName sheetMap
<tr .table__row>
<td .table__td>^{simpleLink (toWidget sheetName) (CSheetR tid ssh csh sheetName SSubsR)}
$if groupsPossible
<td .table__td>#{ciSubmittors}
<td .table__td>#{ciSubmissions}
$maybe ((splus,sfailed),_,_) <- Map.lookup sheetName assignment
$if 0 < Set.size sfailed
<td .table__td>#{ciSubmissions - ciAssigned}
<td .table__td .alert-danger>(-#{show (Set.size splus)}, failed: #{show (Set.size sfailed)})
$elseif 0 < Set.size splus
<td .table__td>#{ciSubmissions - ciAssigned}
<td .table__td .alert-info>(-#{show (Set.size splus)})
$else
<p>
_{MsgCourseParticipants nrParticipants}
<div .scrolltable .scrolltable--bordered .corrections-overview__section>
<table .table .table--striped .table--hover>
<tr .table__row .table__row--head>
<th .table__th rowspan=2>_{MsgSheet}
$if groupsPossible
<th .table__th rowspan=2>_{MsgNrSubmittorsTotal}
<th .table__th rowspan=2>_{MsgNrSubmissionsTotal}
<th .table__th colspan=2>_{MsgNrSubmissionsNotAssigned}
<th .table__th rowspan=2>_{MsgNrSubmissionsNotCorrected}
<th .table__th colspan=3>_{MsgCorrectionTime}
<tr .table__row .table__row--head>
<th .table__th>
<th .table__th>_{MsgGenericNumChange}
<th .table__th>_{MsgGenericMin}
<th .table__th>_{MsgGenericAvg}
<th .table__th>_{MsgGenericMax}
$# Always iterate over orderedSheetNames for consistent sorting! Newest first, except in this table
$forall sheetName <- reverse orderedSheetNames
$maybe CorrectionInfo{ciSubmittors, ciSubmissions, ciAssigned, ciCorrected, ciMin, ciTot, ciMax} <- Map.lookup sheetName sheetMap
<tr .table__row>
<td .table__td>^{simpleLink (toWidget sheetName) (CSheetR tid ssh csh sheetName SSubsR)}
$if groupsPossible
<td .table__td>#{ciSubmittors}
<td .table__td>#{ciSubmissions}
$maybe ((splus,sfailed),_,_) <- Map.lookup sheetName assignment
$if 0 < Set.size sfailed
<td .table__td>#{ciSubmissions - ciAssigned}
<td .table__td .alert-danger>(-#{show (Set.size splus)}, failed: #{show (Set.size sfailed)})
$elseif 0 < Set.size splus
<td .table__td>#{ciSubmissions - ciAssigned}
<td .table__td .alert-info>(-#{show (Set.size splus)})
$else
<td .table__td>#{ciSubmissions - ciAssigned}
<td .table__td>
$nothing
<td .table__td>#{ciSubmissions - ciAssigned}
<td .table__td>
$nothing
<td .table__td>#{ciSubmissions - ciAssigned}
<td .table__td>
<td .table__td>#{ciSubmissions - ciCorrected}
<td .table__td>#{showDiffDays ciMin}
<td .table__td>#{showAvgsDays ciTot ciCorrected}
<td .table__td>#{showDiffDays ciMax}
<td .table__td>#{ciSubmissions - ciCorrected}
<td .table__td>#{showDiffDays ciMin}
<td .table__td>#{showAvgsDays ciTot ciCorrected}
<td .table__td>#{showDiffDays ciMax}
<section>
<h2>_{MsgCorrectionCorrectors}
<table .table .table--striped .table--hover>
<tr .table__row .table__row--head>
<th .table__th rowspan=2>_{MsgCorrector}
<th .table__th colspan=2>_{MsgGenericAll}
<th .table__th rowspan=2>_{MsgCorDeficitProportion}
<th .table__th colspan=3>_{MsgCorrectionTime}
$# Always iterate over orderedSheetNames for consistent sorting! Newest first, except in this table
$forall shn <- orderedSheetNames
<th .table__th colspan=5>
^{simpleLink (toWidget shn) (CSheetR tid ssh csh shn SShowR)}
<tr .table__row .table__row--head>
<th .table__th>_{MsgNrSubmissionsTotal}
<th .table__th>_{MsgNrSubmissionsNotCorrected}
<th .table__th>_{MsgGenericMin}
<th .table__th>_{MsgGenericAvg}
<th .table__th>_{MsgGenericMax}
$# Always iterate over orderedSheetNames for consistent sorting! Newest first, except in this table
$forall _shn <- orderedSheetNames
<th .table__th>_{MsgCorProportion}
<th .table__th>_{MsgNrSubmissionsTotalShort}
<th .table__th>_{MsgGenericNumChange}
<th .table__th>_{MsgNrSubmissionsNotCorrectedShort}
<th .table__th>_{MsgGenericAvg}
$forall (CorrectionInfo{ciCorrector, ciSubmissions=ciSubmissionsNr, ciCorrected, ciMin, ciTot, ciMax}) <- corrInfos
$with (nameW,loadM, name) <- getCorrector ciCorrector
$# TODO: User proper Tooltips instead of title attribute here, once Tooltips work with tables
<tr .table__row title="#{name}">
<td .table__td>^{nameW}
<td .table__td>#{ciSubmissionsNr}
$with total <- ciSubmissions corrMapSum
$if total > 0
\ (#{textPercent' True 0 ciSubmissionsNr total})
<td .table__td .heated style="--hotness: #{heat ciSubmissionsNr ciCorrected}">#{ciSubmissionsNr - ciCorrected}
<td .table__td>
$maybe deficit <- getCorrDeficit ciCorrector
#{rationalToFixed3 deficit}
<td .table__td>#{showDiffDays ciMin}
<td .table__td>#{showAvgsDays ciTot ciCorrected}
<td .table__td>#{showDiffDays ciMax}
$# Always iterate over orderedSheetNames for consistent sorting! Newest first, except in this table
$forall shn <- orderedSheetNames
$maybe CorrectionInfo{ciSubmissions=sheetSubmissionsNr} <- Map.lookup shn sheetMap
<td .table__td>
$maybe SheetCorrector{sheetCorrectorLoad, sheetCorrectorState} <- Map.lookup shn loadM
#{showCompactCorrectorLoad sheetCorrectorLoad sheetCorrectorState}
$if sheetCorrectorState == CorrectorNormal
$maybe Load{byProportion=total} <- Map.lookup shn sheetLoad
$if total > 0
\ (#{textPercent' True 0 (byProportion sheetCorrectorLoad) total})
$maybe CorrectionInfo{ciSubmissions,ciCorrected,ciTot} <- getCorrSheetStatus ciCorrector shn
<td .table__td>#{ciSubmissions}
$if sheetSubmissionsNr > 0
\ (#{textPercent' True 0 ciSubmissions sheetSubmissionsNr})
$maybe nrNew <- getCorrNewAssignment ciCorrector shn
$# <td .table__td>#{ciAssigned} `ciSubmissions` is here always identical to `ciAssigned` and also works for `ciCorrector == Nothing`. ciAssigned only useful in aggregate maps like `sheetMap`
<td .table__td .alert-info>(+#{nrNew})
$nothing
<td .table__td>
<td .table__td .heated style="--hotness: #{heat ciSubmissions ciCorrected}">#{ciSubmissions - ciCorrected}
<td .table__td>#{showAvgsDays ciTot ciCorrected}
$nothing
<td .table__td>
<td .table__td>
<td .table__td>
<td .table__td>
$if not (null orderedSheetNames)
<tr .table__row>
<td .table__th>Σ
$with ciSubmissionsNr <- ciSubmissions corrMapSum
$with ciCorrectedNr <- ciCorrected corrMapSum
<td .table__th>#{ciSubmissionsNr}
<td .table__td .heated style="--hotness: #{heat ciSubmissionsNr ciCorrectedNr}">#{ciSubmissionsNr - ciCorrectedNr}
<td .table__th>#{ciCorrected corrMapSum}
<td .table__th>#{showDiffDays (ciMin corrMapSum)}
<td .table__th>#{showAvgsDays (ciTot corrMapSum) (ciCorrected corrMapSum)}
<td .table__th>#{showDiffDays (ciMax corrMapSum)}
$# Always iterate over orderedSheetNames for consistent sorting! Newest first, except in this table
$forall shn <- orderedSheetNames
$maybe CorrectionInfo{ciSubmissions} <- Map.lookup shn sheetMap
<td .table__th>#{getLoadSum shn}
<td .table__th>#{ciSubmissions}
<td .table__td colspan=3>^{simpleLinkI (SomeMessage MsgMenuCorrectorsChange) (CSheetR tid ssh csh shn SEditR)}
<div .scrolltable .scrolltable--bordered>
<table .table .table--striped .table--hover>
<tr .table__row .table__row--head>
<th>
<th colspan=2>
<th>
<th colspan=3>
<th .table__th rowspan=2>_{MsgCorrector}
<th .table__th colspan=2>_{MsgGenericAll}
<th .table__th rowspan=2>_{MsgCorDeficitProportion}
<th .table__th colspan=3>_{MsgCorrectionTime}
$# Always iterate over orderedSheetNames for consistent sorting! Newest first, except in this table
$forall shn <- orderedSheetNames
<th .table__th colspan=5>
^{simpleLink (toWidget shn) (CSheetR tid ssh csh shn SShowR)}
<tr .table__row .table__row--head>
<th .table__th>_{MsgNrSubmissionsTotal}
<th .table__th>_{MsgNrSubmissionsNotCorrected}
<th .table__th>_{MsgGenericMin}
<th .table__th>_{MsgGenericAvg}
<th .table__th>_{MsgGenericMax}
$# Always iterate over orderedSheetNames for consistent sorting! Newest first, except in this table
$forall _shn <- orderedSheetNames
<th .table__th>_{MsgCorProportion}
<th .table__th>_{MsgNrSubmissionsTotalShort}
<th .table__th>_{MsgGenericNumChange}
<th .table__th>_{MsgNrSubmissionsNotCorrectedShort}
<th .table__th>_{MsgGenericAvg}
$forall (CorrectionInfo{ciCorrector, ciSubmissions=ciSubmissionsNr, ciCorrected, ciMin, ciTot, ciMax}) <- corrInfos
$with (nameW,loadM, name) <- getCorrector ciCorrector
$# TODO: User proper Tooltips instead of title attribute here, once Tooltips work with tables
<tr .table__row title="#{name}">
<td .table__td>^{nameW}
<td .table__td>#{ciSubmissionsNr}
$with total <- ciSubmissions corrMapSum
$if total > 0
\ (#{textPercent' True 0 ciSubmissionsNr total})
<td .table__td .heated style="--hotness: #{heat ciSubmissionsNr ciCorrected}">#{ciSubmissionsNr - ciCorrected}
<td .table__td>
$maybe deficit <- getCorrDeficit ciCorrector
#{rationalToFixed3 deficit}
<td .table__td>#{showDiffDays ciMin}
<td .table__td>#{showAvgsDays ciTot ciCorrected}
<td .table__td>#{showDiffDays ciMax}
$# Always iterate over orderedSheetNames for consistent sorting! Newest first, except in this table
$forall shn <- orderedSheetNames
$maybe CorrectionInfo{ciSubmissions=sheetSubmissionsNr} <- Map.lookup shn sheetMap
<td .table__td>
$maybe SheetCorrector{sheetCorrectorLoad, sheetCorrectorState} <- Map.lookup shn loadM
#{showCompactCorrectorLoad sheetCorrectorLoad sheetCorrectorState}
$if sheetCorrectorState == CorrectorNormal
$maybe Load{byProportion=total} <- Map.lookup shn sheetLoad
$if total > 0
\ (#{textPercent' True 0 (byProportion sheetCorrectorLoad) total})
$maybe CorrectionInfo{ciSubmissions,ciCorrected,ciTot} <- getCorrSheetStatus ciCorrector shn
<td .table__td>#{ciSubmissions}
$if sheetSubmissionsNr > 0
\ (#{textPercent' True 0 ciSubmissions sheetSubmissionsNr})
$maybe nrNew <- getCorrNewAssignment ciCorrector shn
$# <td .table__td>#{ciAssigned} `ciSubmissions` is here always identical to `ciAssigned` and also works for `ciCorrector == Nothing`. ciAssigned only useful in aggregate maps like `sheetMap`
<td .table__td .alert-info>(+#{nrNew})
$nothing
<td .table__td>
<td .table__td .heated style="--hotness: #{heat ciSubmissions ciCorrected}">#{ciSubmissions - ciCorrected}
<td .table__td>#{showAvgsDays ciTot ciCorrected}
$nothing
<td .table__td>
<td .table__td>
<td .table__td>
<td .table__td>
$if not (null orderedSheetNames)
<tr .table__row>
<td .table__th>Σ
$with ciSubmissionsNr <- ciSubmissions corrMapSum
$with ciCorrectedNr <- ciCorrected corrMapSum
<td .table__th>#{ciSubmissionsNr}
<td .table__td .heated style="--hotness: #{heat ciSubmissionsNr ciCorrectedNr}">#{ciSubmissionsNr - ciCorrectedNr}
<td .table__th>#{ciCorrected corrMapSum}
<td .table__th>#{showDiffDays (ciMin corrMapSum)}
<td .table__th>#{showAvgsDays (ciTot corrMapSum) (ciCorrected corrMapSum)}
<td .table__th>#{showDiffDays (ciMax corrMapSum)}
$# Always iterate over orderedSheetNames for consistent sorting! Newest first, except in this table
$forall shn <- orderedSheetNames
$maybe CorrectionInfo{ciSubmissions} <- Map.lookup shn sheetMap
<td .table__th>#{getLoadSum shn}
<td .table__th>#{ciSubmissions}
<td .table__td colspan=3>^{simpleLinkI (SomeMessage MsgMenuCorrectorsChange) (CSheetR tid ssh csh shn SEditR)}
^{btnWdgt}
$if not (null assignment)
<tr .table__row>
<td>
<td colspan=2>
<td>
<td colspan=3>
$# Always iterate over orderedSheetNames for consistent sorting! Newest first, except in this table
$forall shn <- orderedSheetNames
<td .table__td colspan=5>
$maybe btnView <- sheetBtnViews !? shn
$if has (ix shn) assignment
^{fvWidget btnView}
<tr .table__row .table__row--head>
<td>
<td colspan=2>
<td>
<td colspan=3>
$# Always iterate over orderedSheetNames for consistent sorting! Newest first, except in this table
$forall shn <- orderedSheetNames
<td .table__th colspan=5>
^{simpleLink (toWidget shn) (CSheetR tid ssh csh shn SShowR)}
<p .explanation>
_{MsgAssignSubmissionsRandomWarning}
$if not (null assignment)
$maybe btnView <- btnViews !? Nothing
<section>
<p>
_{MsgAssignSubmissionsAssignableSheets}
<div .corrections-overview__section>
<ul>
$forall shn <- assignSheetNames'
<li>
<a href=@{CSheetR tid ssh csh shn SShowR}>
#{shn}
<p>
^{fvWidget btnView}
<section>
<p>_{MsgAssignSubmissionsRandomWarning}