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-size: 14px
font-family: monospace 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 max-height: 75vh
overflow: auto overflow: auto

View File

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

View File

@ -32,6 +32,7 @@ BtnLecInvDecline: Ablehnen
BtnCorrInvAccept: Annehmen BtnCorrInvAccept: Annehmen
BtnCorrInvDecline: Ablehnen BtnCorrInvDecline: Ablehnen
BtnSubmissionsAssign: Abgaben automatisch zuteilen BtnSubmissionsAssign: Abgaben automatisch zuteilen
BtnSubmissionsAssignAll: Abgaben automatisch zuteilen
BtnAllocationCompute: Vergabe berechnen BtnAllocationCompute: Vergabe berechnen
BtnAllocationAccept: Vergabe akzeptieren BtnAllocationAccept: Vergabe akzeptieren
BtnSystemMessageHide: Verstecken BtnSystemMessageHide: Verstecken
@ -633,6 +634,7 @@ NrSubmissionsNotCorrected: Unkorrigiert
NrSubmissionsNotCorrectedShort: Unkg. NrSubmissionsNotCorrectedShort: Unkg.
CorrectionTime: Korrekturdauer 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. 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: CorrectionsUploaded num@Int64: #{num} Korrekturen wurden gespeichert:
NoCorrectionsUploaded: In der hochgeladenen Datei wurden keine Korrekturen gefunden. NoCorrectionsUploaded: In der hochgeladenen Datei wurden keine Korrekturen gefunden.

View File

@ -32,6 +32,7 @@ BtnLecInvDecline: Decline
BtnCorrInvAccept: Accept BtnCorrInvAccept: Accept
BtnCorrInvDecline: Decline BtnCorrInvDecline: Decline
BtnSubmissionsAssign: Assign submissions automatically BtnSubmissionsAssign: Assign submissions automatically
BtnSubmissionsAssignAll: Automatically distribute corrections
BtnAllocationCompute: Compute allocation BtnAllocationCompute: Compute allocation
BtnAllocationAccept: Accept allocation BtnAllocationAccept: Accept allocation
BtnSystemMessageHide: Hide BtnSystemMessageHide: Hide
@ -631,6 +632,7 @@ NrSubmissionsNotCorrected: Not corrected
NrSubmissionsNotCorrectedShort: N.corr. NrSubmissionsNotCorrectedShort: N.corr.
CorrectionTime: Correction time 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. 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"}: CorrectionsUploaded num: Successfully saved #{num} #{pluralEN num "correction" "corrections"}:
NoCorrectionsUploaded: No corrections could be found within the uploaded file. NoCorrectionsUploaded: No corrections could be found within the uploaded file.

View File

@ -4,7 +4,7 @@ module Handler.Submission.Assign
, getSAssignR, postSAssignR , getSAssignR, postSAssignR
) where ) where
import Import hiding (link) import Import hiding (link, unzip)
import Handler.Utils hiding (colSchool) import Handler.Utils hiding (colSchool)
import Handler.Utils.Corrections import Handler.Utils.Corrections
@ -12,7 +12,7 @@ import Handler.Utils.Submission
import Data.List as List (foldl, foldr) import Data.List as List (foldl, foldr)
import qualified Data.Set as Set import qualified Data.Set as Set
import Data.Map.Strict ((!)) import Data.Map.Strict ((!), (!?))
import qualified Data.Map.Strict as Map import qualified Data.Map.Strict as Map
import qualified Data.Text as Text import qualified Data.Text as Text
@ -20,6 +20,8 @@ import qualified Data.CaseInsensitive as CI
import qualified Database.Esqueleto as E import qualified Database.Esqueleto as E
import Data.List.NonEmpty (unzip)
getSubAssignR, postSubAssignR :: TermId -> SchoolId -> CourseShorthand -> SheetName -> CryptoFileNameSubmission -> Handler Html getSubAssignR, postSubAssignR :: TermId -> SchoolId -> CourseShorthand -> SheetName -> CryptoFileNameSubmission -> Handler Html
getSubAssignR = postSubAssignR getSubAssignR = postSubAssignR
@ -56,15 +58,6 @@ postSubAssignR tid ssh csh shn cID = do
$(widgetFile "submission-assign") $(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 :: TermId -> SchoolId -> CourseShorthand -> Handler Html
getCAssignR = postCAssignR getCAssignR = postCAssignR
postCAssignR tid ssh csh = do postCAssignR tid ssh csh = do
@ -77,35 +70,22 @@ postSAssignR tid ssh csh shn = do
(shid,cid) <- runDB $ fetchSheetIdCourseId tid ssh csh shn (shid,cid) <- runDB $ fetchSheetIdCourseId tid ssh csh shn
assignHandler tid ssh csh cid [shid] 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 :: TermId -> SchoolId -> CourseShorthand -> CourseId -> [SheetId] -> Handler Html
assignHandler tid ssh csh cid assignSids = do assignHandler tid ssh csh cid assignSids = do
-- evaluate form first, since it affects DB action currentRoute <- fromMaybe (error "assignHandler called from 404-handler") <$> liftHandler getCurrentRoute
(btnWdgt, btnResult) <- runButtonForm FIDAssignSubmissions
-- gather data -- 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 -- cid <- getKeyBy404 $ TermSchoolCourseShort tid ssh csh
nrParticipants <- count [CourseParticipantCourse ==. cid, CourseParticipantState ==. CourseParticipantActive ] nrParticipants <- count [CourseParticipantCourse ==. cid, CourseParticipantState ==. CourseParticipantActive ]
sheetList <- selectList [SheetCourse ==. cid] [Desc SheetActiveTo, Desc SheetActiveFrom] 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 let orderedSheetNames = fmap (\(Entity _ Sheet{sheetName}) -> sheetName) sheetList
sheets = entities2map sheetList sheets = entities2map sheetList
sheetIds = Map.keys sheets 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 let foldFun (Entity _ Sheet{sheetGrouping=sgr}) acc = acc || sgr /= NoGroups
in List.foldr foldFun False sheetList 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
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 -- 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)) 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 ignoreExceptions (SubmissionsNotFound _sids_not_found) = return mempty -- cannot happen, since last argument to planSubmissions is Nothing
(plan,deficit) <- lift $ handle ignoreExceptions $ planSubmissions sid Nothing (plan,deficit) <- lift $ handle ignoreExceptions $ planSubmissions sid Nothing
guard $ not $ null plan -- only proceed if there is a plan for this sheet guard $ not $ null plan -- only proceed if there is a plan for this sheet
-- implement assignment plan status@(sub_ok, sub_fail) <- fmap fold . formResultMaybe btnResult $ \btn -> lift . maybeT (return Nothing) $ do
status <- lift $ case btnResult of guard $ case btn of
Nothing -> return (Set.empty, Set.empty) BtnSubmissionsAssignAll -> True
(Just BtnSubmissionsAssign) -> do BtnSubmissionsAssign shn' -> shn' == shn
status@(sub_ok,sub_fail) <- writeSubmissionPlan plan
let nr_ok = olength sub_ok status@(sub_ok,sub_fail) <- lift $ writeSubmissionPlan plan
nr_fail = olength sub_fail let nr_ok = olength sub_ok
alert_ok = toMaybe (nr_ok > 0) $ SomeMessage $ MsgUpdatedSheetCorrectorsAutoAssigned nr_ok nr_fail = olength sub_fail
alert_fail = toMaybe (nr_fail > 0) $ SomeMessage $ MsgUpdatedSheetCorrectorsAutoFailed nr_fail alert_ok = toMaybe (nr_ok > 0) $ SomeMessage $ MsgUpdatedSheetCorrectorsAutoAssigned nr_ok
msg_status = bool Success Error $ nr_fail > 0 alert_fail = toMaybe (nr_fail > 0) $ SomeMessage $ MsgUpdatedSheetCorrectorsAutoFailed nr_fail
msg_header = SomeMessage $ shn <> ":" msg_status = bool Success Error $ nr_fail > 0
when (nr_ok > 0 || nr_fail > 0) $ msg_header = SomeMessage $ shn <> ":"
addMessageI msg_status $ UniWorXMessages $ msg_header : catMaybes [alert_ok, alert_fail] if | nr_ok > 0 || nr_fail > 0 -> do
return status addMessageI msg_status $ UniWorXMessages $ msg_header : catMaybes [alert_ok, alert_fail]
return $ Map.insert shn (status, countMapElems plan, deficit) acc return $ Just status
assignSids' <- if null assignSids -- assignAll; we distinguish assignSids' here avoid useless Alerts | otherwise -> do
then selectKeysList [SheetCourse ==. cid] [Asc SheetActiveTo] addMessageI Error $ MsgSheetsUnassignable $ CI.original shn
else return assignSids 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' assignment <- foldM buildA Map.empty assignSids'
correctors <- E.select . E.from $ \(corrector `E.InnerJoin` user) -> do 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 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 let -- infoMap :: Map SheetName (Map (Maybe UserId) CorrectionInfo) -- repeated here for easier reference
-- create aggregate maps -- create aggregate maps
@ -268,14 +259,39 @@ assignHandler tid ssh csh cid assignSids = do
showAvgsDays :: Maybe NominalDiffTime -> Integer -> Text showAvgsDays :: Maybe NominalDiffTime -> Integer -> Text
showAvgsDays Nothing _ = mempty showAvgsDays Nothing _ = mempty
showAvgsDays (Just dt) n = formatDiffDays $ dt / fromIntegral n showAvgsDays (Just dt) n = formatDiffDays $ dt / fromIntegral n
let headingShort let headingShort = MsgMenuCorrectionsAssign
| 0 < Map.size assignment = MsgMenuCorrectionsAssignSheet $ Text.intercalate ", " $ fmap CI.original $ Map.keys assignment headingLong = prependCourseTitle tid ssh csh MsgMenuCorrectionsAssign
| otherwise = MsgMenuCorrectionsAssign
headingLong = prependCourseTitle tid ssh csh MsgMenuCorrectionsAssign
unassignableSheets = filter (\shn -> Map.notMember shn assignment) assignSheetNames unassignableSheets = filter (\shn -> Map.notMember shn assignment) assignSheetNames
unless (null unassignableSheets) $ addMessageI Warning $ MsgSheetsUnassignable $ Text.intercalate ", " $ fmap CI.original unassignableSheets unless (null unassignableSheets) $ addMessageI Warning $ MsgSheetsUnassignable $ Text.intercalate ", " $ fmap CI.original unassignableSheets
siteLayoutMsg headingShort $ do siteLayoutMsg headingShort $ do
setTitleI headingLong 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> <section>
<h2>_{MsgCorrectionSheets} <h2>_{MsgCorrectionSheets}
_{MsgCourseParticipants nrParticipants} <p>
<table .table .table--striped .table--hover> _{MsgCourseParticipants nrParticipants}
<tr .table__row .table__row--head>
<th .table__th rowspan=2>_{MsgSheet} <div .scrolltable .scrolltable--bordered .corrections-overview__section>
$if groupsPossible <table .table .table--striped .table--hover>
<th .table__th rowspan=2>_{MsgNrSubmittorsTotal} <tr .table__row .table__row--head>
<th .table__th rowspan=2>_{MsgNrSubmissionsTotal} <th .table__th rowspan=2>_{MsgSheet}
<th .table__th colspan=2>_{MsgNrSubmissionsNotAssigned} $if groupsPossible
<th .table__th rowspan=2>_{MsgNrSubmissionsNotCorrected} <th .table__th rowspan=2>_{MsgNrSubmittorsTotal}
<th .table__th colspan=3>_{MsgCorrectionTime} <th .table__th rowspan=2>_{MsgNrSubmissionsTotal}
<tr .table__row .table__row--head> <th .table__th colspan=2>_{MsgNrSubmissionsNotAssigned}
<th .table__th> <th .table__th rowspan=2>_{MsgNrSubmissionsNotCorrected}
<th .table__th>_{MsgGenericNumChange} <th .table__th colspan=3>_{MsgCorrectionTime}
<th .table__th>_{MsgGenericMin} <tr .table__row .table__row--head>
<th .table__th>_{MsgGenericAvg} <th .table__th>
<th .table__th>_{MsgGenericMax} <th .table__th>_{MsgGenericNumChange}
$# Always iterate over orderedSheetNames for consistent sorting! Newest first, except in this table <th .table__th>_{MsgGenericMin}
$forall sheetName <- reverse orderedSheetNames <th .table__th>_{MsgGenericAvg}
$maybe CorrectionInfo{ciSubmittors, ciSubmissions, ciAssigned, ciCorrected, ciMin, ciTot, ciMax} <- Map.lookup sheetName sheetMap <th .table__th>_{MsgGenericMax}
<tr .table__row> $# Always iterate over orderedSheetNames for consistent sorting! Newest first, except in this table
<td .table__td>^{simpleLink (toWidget sheetName) (CSheetR tid ssh csh sheetName SSubsR)} $forall sheetName <- reverse orderedSheetNames
$if groupsPossible $maybe CorrectionInfo{ciSubmittors, ciSubmissions, ciAssigned, ciCorrected, ciMin, ciTot, ciMax} <- Map.lookup sheetName sheetMap
<td .table__td>#{ciSubmittors} <tr .table__row>
<td .table__td>#{ciSubmissions} <td .table__td>^{simpleLink (toWidget sheetName) (CSheetR tid ssh csh sheetName SSubsR)}
$maybe ((splus,sfailed),_,_) <- Map.lookup sheetName assignment $if groupsPossible
$if 0 < Set.size sfailed <td .table__td>#{ciSubmittors}
<td .table__td>#{ciSubmissions - ciAssigned} <td .table__td>#{ciSubmissions}
<td .table__td .alert-danger>(-#{show (Set.size splus)}, failed: #{show (Set.size sfailed)}) $maybe ((splus,sfailed),_,_) <- Map.lookup sheetName assignment
$elseif 0 < Set.size splus $if 0 < Set.size sfailed
<td .table__td>#{ciSubmissions - ciAssigned} <td .table__td>#{ciSubmissions - ciAssigned}
<td .table__td .alert-info>(-#{show (Set.size splus)}) <td .table__td .alert-danger>(-#{show (Set.size splus)}, failed: #{show (Set.size sfailed)})
$else $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>#{ciSubmissions - ciAssigned}
<td .table__td> <td .table__td>
$nothing <td .table__td>#{ciSubmissions - ciCorrected}
<td .table__td>#{ciSubmissions - ciAssigned} <td .table__td>#{showDiffDays ciMin}
<td .table__td> <td .table__td>#{showAvgsDays ciTot ciCorrected}
<td .table__td>#{ciSubmissions - ciCorrected} <td .table__td>#{showDiffDays ciMax}
<td .table__td>#{showDiffDays ciMin}
<td .table__td>#{showAvgsDays ciTot ciCorrected}
<td .table__td>#{showDiffDays ciMax}
<section> <section>
<h2>_{MsgCorrectionCorrectors} <h2>_{MsgCorrectionCorrectors}
<table .table .table--striped .table--hover> <div .scrolltable .scrolltable--bordered>
<tr .table__row .table__row--head> <table .table .table--striped .table--hover>
<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)}
<tr .table__row .table__row--head> <tr .table__row .table__row--head>
<th> <th .table__th rowspan=2>_{MsgCorrector}
<th colspan=2> <th .table__th colspan=2>_{MsgGenericAll}
<th> <th .table__th rowspan=2>_{MsgCorDeficitProportion}
<th colspan=3> <th .table__th colspan=3>_{MsgCorrectionTime}
$# Always iterate over orderedSheetNames for consistent sorting! Newest first, except in this table $# Always iterate over orderedSheetNames for consistent sorting! Newest first, except in this table
$forall shn <- orderedSheetNames $forall shn <- orderedSheetNames
<th .table__th colspan=5> <th .table__th colspan=5>
^{simpleLink (toWidget shn) (CSheetR tid ssh csh shn SShowR)} ^{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}