chore(users-add): confirm stub, migrate buttons

This commit is contained in:
Sarah Vaupel 2022-12-10 23:23:42 +01:00
parent e65d38898e
commit dfc017b10a
9 changed files with 183 additions and 12 deletions

View File

@ -121,6 +121,8 @@ CourseParticipantsAlreadyRegistered n@Int: #{n} #{pluralDE n "Teinehmer:in" "Tei
CourseParticipantsAlreadyTutorialMember n@Int: #{n} #{pluralDE n "Teinehmer:in" "Teilnehmer:innen"} #{pluralDE n "ist" "sind"} bereits in dieser Übungsgruppe angemeldet
CourseParticipantsRegistered n@Int: #{n} #{pluralDE n "Teinehmer:in" "Teilnehmer:innen"} erfolgreich angemeldet
CourseParticipantsRegisteredTutorial n@Int: #{n} #{pluralDE n "Teinehmer:in" "Teilnehmer:innen"} erfolgreich zur Übungsgruppe angemeldet
CourseParticipantsRegisterConfirmationHeading: Teilnehmer:innen hinzufügen
CourseApplicationText: Text-Bewerbung
CourseApplicationFollowInstructions: Beachten Sie die Anweisungen zur Bewerbung!
CourseRegistrationText: Text zur Anmeldung

View File

@ -121,6 +121,8 @@ CourseParticipantsAlreadyRegistered n: #{n} #{pluralEN n "participant is" "parti
CourseParticipantsAlreadyTutorialMember n: #{n} #{pluralEN n "participant is" "participants are"} already registered for this tutorial
CourseParticipantsRegistered n: Successfully registered #{n} #{pluralEN n "participant" "participants"}
CourseParticipantsRegisteredTutorial n: Successfully registered #{n} #{pluralEN n "participant" "participants"} for tutorial
CourseParticipantsRegisterConfirmationHeading: Register participants
CourseApplicationText: Application text
CourseApplicationFollowInstructions: Please follow the instructions for applications!
CourseRegistrationText: Registration text

View File

@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>,Winnie Ros <winnie.ros@campus.lmu.de>
# SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>,Winnie Ros <winnie.ros@campus.lmu.de>,Sarah Vaupel <sarah.vaupel@ifi.lmu.de>
#
# SPDX-License-Identifier: AGPL-3.0-or-later
@ -65,4 +65,7 @@ BtnNotifyNewCourseForceOff: Nicht benachrichtigen
BtnUserAssimilate: Assimilieren
BtnCloseExam: Prüfung abschließen
BtnFinishExam: Prüfungsergebnisse sichtbar schalten
BtnConfirm: Bestätigen
BtnConfirm: Bestätigen
BtnCourseRegisterAdd: Personen suchen
BtnCourseRegisterConfirm: Ausgewählte Personen anmelden
BtnCourseRegisterAbort: Abbrechen

View File

@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2022 Steffen Jost <jost@tcs.ifi.lmu.de>,Winnie Ros <winnie.ros@campus.lmu.de>
# SPDX-FileCopyrightText: 2022 Steffen Jost <jost@tcs.ifi.lmu.de>,Winnie Ros <winnie.ros@campus.lmu.de>,Sarah Vaupel <sarah.vaupel@ifi.lmu.de>
#
# SPDX-License-Identifier: AGPL-3.0-or-later
@ -65,4 +65,7 @@ BtnNotifyNewCourseForceOff: Do not notify me
BtnUserAssimilate: Assimilate
BtnCloseExam: Close exam
BtnFinishExam: Make results visible
BtnConfirm: Confirm
BtnConfirm: Confirm
BtnCourseRegisterAdd: Search persons
BtnCourseRegisterConfirm: Register selected persons
BtnCourseRegisterAbort: Abort

View File

@ -13,9 +13,7 @@ import Handler.Utils.Avs
import Jobs.Queue
--import Data.Aeson hiding (Result(..))
import qualified Data.CaseInsensitive as CI
--import qualified Data.HashSet as HashSet
import Data.List (genericLength)
import qualified Data.Map as Map
import qualified Data.Text as Text
@ -26,6 +24,26 @@ import Control.Monad.Except (MonadError(..))
import Generics.Deriving.Monoid (memptydefault, mappenddefault)
data ButtonCourseRegisterMode = BtnCourseRegisterAdd | BtnCourseRegisterConfirm | BtnCourseRegisterAbort
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
instance Universe ButtonCourseRegisterMode
instance Finite ButtonCourseRegisterMode
embedRenderMessage ''UniWorX ''ButtonCourseRegisterMode id
nullaryPathPiece ''ButtonCourseRegisterMode $ camelToPathPiece' 1
instance Button UniWorX ButtonCourseRegisterMode where
btnLabel x = [whamlet|_{x}|]
btnClasses BtnCourseRegisterAdd = [BCIsButton, BCPrimary]
btnClasses BtnCourseRegisterConfirm = [BCIsButton, BCPrimary]
btnClasses BtnCourseRegisterAbort = [BCIsButton, BCDanger]
btnValidate _ BtnCourseRegisterAbort = False
btnValidate _ _ = True
data AddUsers = AddUsers
{ auUsers :: Set Text
, auTutorial :: Maybe (CI Text)
@ -120,11 +138,32 @@ postCAddUserR tid ssh csh = do
}
--confirmAddUser :: ()
--confirmAddUser = do
-- siteLayoutMsg MsgCourseParticipantsRegisterConfirmationHeading $ do
-- setTitleI MsgCourseParticipantsRegisterConfirmationHeading
confirmAddUser :: Handler Html
confirmAddUser = do
siteLayoutMsg MsgCourseParticipantsRegisterConfirmationHeading $ do
setTitleI MsgCourseParticipantsRegisterConfirmationHeading
let
confirmCheckBox :: Widget
confirmCheckBox = do
let sJsonField :: Field (HandlerFor UniWorX) a
sJsonField = secretJsonField' $ \theId name attrs val _isReq ->
[whamlet|
$newline never
<input id=#{theId} *{attrs} type=checkbox name=#{name} value=#{either id id val} checked>
|]
fieldView sJsonField act mempty vAttrs (Right act) False
availableActs :: Widget
availableActs = fieldView (secretJsonField :: Field Handler (Set csvAction)) "" mempty [] (Right . Set.empty) False
(confirmForm', confirmEnctype) <- liftHandler . generateFormPost . withButtonForm' [BtnCourseRegisterConfirm, BtnCourseRegisterAbort] . identifyForm FIDCourseRegisterConfirm $ \csrf -> return (error "No meaningful FormResult", $(widgetFile "course/add-user/confirmation"))
let confirmForm = wrapForm confirmForm' FormSettings
{ formMethod = POST
, formAction = Just $ tblLink id
, formEncoding = confirmEnctype
, formAttrs = []
, formSubmit = FormNoSubmit
, formAnchor = Nothing :: Maybe Text
}
$(widgetFile "course/add-user/confirmation-wrapper")
registerUsers :: CourseId -> Map Text (Maybe UserId) -> WriterT [Message] (YesodJobDB UniWorX) ()

View File

@ -283,7 +283,7 @@ data FormIdentifier
| FIDDBTableCsvImportConfirm Text
| FIDDelete
| FIDPrintAcknowledge
| FIDCourseRegister
| FIDCourseRegister | FIDCourseRegisterConfirm
| FIDuserRights
| FIDUserSystemFunctions
| FIDcUserNote

View File

@ -0,0 +1,10 @@
$newline never
$# SPDX-FileCopyrightText: 2022 Sarah Vaupel <sarah.vaupel@ifi.lmu.de>
$#
$# SPDX-License-Identifier: AGPL-3.0-or-later
<section>
<p>_{MsgCourseAddUserConfirmationTip}
<section>
^{confirmForm}

View File

@ -0,0 +1,27 @@
$newline never
$# SPDX-FileCopyrightText: 2022 Sarah Vaupel <sarah.vaupel@ifi.lmu.de>
$#
$# SPDX-License-Identifier: AGPL-3.0-or-later
#{csrf}
^{availableActs}
<div .actions>
$forall actionClass <- sortOn dbtCsvCoarsenActionClass (Map.keys actionMap)
<div .action>
<input type=checkbox id=#{actionClassIdent actionClass} .action__checkbox :defaultChecked actionClass:checked>
<label .action__label for=#{actionClassIdent actionClass}>
^{dbtCsvRenderActionClass actionClass}
<fieldset .action__fieldset uw-interactive-fieldset .interactive-fieldset__target data-conditional-input=#{actionClassIdent actionClass}>
<div .action__checked-counter>
<div .action__toggle-all>
<input type=checkbox id=#{actionClassIdent actionClass}-toggle-all>
<label for=#{actionClassIdent actionClass}-toggle-all .action__option-label>
_{MsgDBCsvImportActionToggleAll}
<div .action__options>
$forall action <- Set.toList (actionMap ! actionClass)
<div .action__option>
^{csvActionCheckBox [] action}
<label .action__option-label for=#{actionIdent action}>
^{dbtCsvRenderKey existing action}

View File

@ -0,0 +1,85 @@
// SPDX-FileCopyrightText: 2022 Sarah Vaupel <sarah.vaupel@ifi.lmu.de>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(function() {
var IMPORT_ACTIONS_SELECTOR = '.actions';
var IMPORT_ACTION_SELECTOR = '.action';
var IMPORT_ACTION_CHECKBOX_SELECTOR = '.action__checkbox ';
var IMPORT_ACTION_OPTIONS_SELECTOR = '.action__options';
var IMPORT_ACTION_TOGGLE_ALL_SELECTOR = '.action__toggle-all [type="checkbox"]';
var IMPORT_ACTION_CHECKED_COUNTER_SELECTOR = '.action__checked-counter';
var importActionsElement;
document.addEventListener('DOMContentLoaded', function() {
importActionsElement = document.querySelector(IMPORT_ACTIONS_SELECTOR);
setupActions();
});
function setupActions() {
var actionElements = Array.from(importActionsElement.querySelectorAll(IMPORT_ACTION_SELECTOR));
actionElements.forEach(function(element) {
setupAction(element);
});
}
function setupAction(actionElement) {
var actionCheckbox = actionElement.querySelector(IMPORT_ACTION_CHECKBOX_SELECTOR);
var actionOptions = actionElement.querySelector(IMPORT_ACTION_OPTIONS_SELECTOR);
if (actionOptions) {
var actionCheckboxes = Array.from(actionOptions.querySelectorAll('[type="checkbox"]'));
var toggleAllCheckbox = actionElement.querySelector(IMPORT_ACTION_TOGGLE_ALL_SELECTOR);
// setup action checkbox to toggle all child checkboxes if changed
actionCheckbox.addEventListener('change', function() {
actionCheckboxes.forEach(function(checkbox) {
checkbox.checked = actionCheckbox.checked;
});
updateCheckedCounter(actionElement, actionCheckboxes);
updateToggleAllCheckbox(toggleAllCheckbox, actionCheckboxes);
});
// update counter and toggle checkbox initially
updateCheckedCounter(actionElement, actionCheckboxes);
updateToggleAllCheckbox(toggleAllCheckbox, actionCheckboxes);
// register change listener for individual checkboxes
actionCheckboxes.forEach(function(checkbox) {
checkbox.addEventListener('change', function() {
updateCheckedCounter(actionElement, actionCheckboxes);
updateToggleAllCheckbox(toggleAllCheckbox, actionCheckboxes);
});
});
// register change listener for toggle all checkbox
if (toggleAllCheckbox) {
toggleAllCheckbox.addEventListener('change', function() {
actionCheckboxes.forEach(function(checkbox) {
checkbox.checked = toggleAllCheckbox.checked;
});
updateCheckedCounter(actionElement, actionCheckboxes);
});
}
}
}
// update checked state of toggle all checkbox based on all other checkboxes
function updateToggleAllCheckbox(toggleAllCheckbox, actionCheckboxes) {
var allChecked = actionCheckboxes.reduce(function(acc, checkbox) {
return acc && checkbox.checked;
}, true);
toggleAllCheckbox.checked = allChecked;
}
// update value of checked counter
function updateCheckedCounter(actionElement, actionCheckboxes) {
var checkedCounter = actionElement.querySelector(IMPORT_ACTION_CHECKED_COUNTER_SELECTOR);
var checkedCheckboxes = actionCheckboxes.reduce(function(acc, checkbox) { return checkbox.checked ? acc + 1 : acc; }, 0);
checkedCounter.innerHTML = checkedCheckboxes + '/' + actionCheckboxes.length;
}
})();