localstorage for show-hides, sortable tables, more navigation

This commit is contained in:
Felix Hamann 2018-03-11 23:49:18 +01:00
parent dd07a1307f
commit 475411bb4a
16 changed files with 248 additions and 173 deletions

View File

@ -90,6 +90,7 @@ data MenuTypes
= NavbarLeft { menuItem :: MenuItem } = NavbarLeft { menuItem :: MenuItem }
| NavbarRight { menuItem :: MenuItem } | NavbarRight { menuItem :: MenuItem }
| NavbarExtra { menuItem :: MenuItem } | NavbarExtra { menuItem :: MenuItem }
| NavbarSecondary { menuItem :: MenuItem }
-- | A convenient synonym for creating forms. -- | A convenient synonym for creating forms.
type Form x = Html -> MForm (HandlerT UniWorX IO) (FormResult x, Widget) type Form x = Html -> MForm (HandlerT UniWorX IO) (FormResult x, Widget)
@ -277,7 +278,7 @@ instance YesodBreadcrumbs UniWorX where
defaultLinks :: [MenuTypes] defaultLinks :: [MenuTypes]
defaultLinks = -- Define the menu items of the header. defaultLinks = -- Define the menu items of the header.
[ NavbarLeft $ MenuItem [ NavbarRight $ MenuItem
{ menuItemLabel = "Home" { menuItemLabel = "Home"
, menuItemRoute = HomeR , menuItemRoute = HomeR
, menuItemAccessCallback = return True , menuItemAccessCallback = return True
@ -287,7 +288,7 @@ defaultLinks = -- Define the menu items of the header.
, menuItemRoute = CourseListR , menuItemRoute = CourseListR
, menuItemAccessCallback = return True , menuItemAccessCallback = return True
} }
, NavbarRight $ MenuItem , NavbarLeft $ MenuItem
{ menuItemLabel = "Users" { menuItemLabel = "Users"
, menuItemRoute = UsersR , menuItemRoute = UsersR
, menuItemAccessCallback = return True -- Creates a LOOP: (Authorized ==) <$> isAuthorized UsersR False , menuItemAccessCallback = return True -- Creates a LOOP: (Authorized ==) <$> isAuthorized UsersR False
@ -297,12 +298,12 @@ defaultLinks = -- Define the menu items of the header.
, menuItemRoute = ProfileR , menuItemRoute = ProfileR
, menuItemAccessCallback = isJust <$> maybeAuthPair , menuItemAccessCallback = isJust <$> maybeAuthPair
} }
, NavbarRight $ MenuItem , NavbarSecondary $ MenuItem
{ menuItemLabel = "Login" { menuItemLabel = "Login"
, menuItemRoute = AuthR LoginR , menuItemRoute = AuthR LoginR
, menuItemAccessCallback = isNothing <$> maybeAuthPair , menuItemAccessCallback = isNothing <$> maybeAuthPair
} }
, NavbarRight $ MenuItem , NavbarSecondary $ MenuItem
{ menuItemLabel = "Logout" { menuItemLabel = "Logout"
, menuItemRoute = AuthR LogoutR , menuItemRoute = AuthR LogoutR
, menuItemAccessCallback = isJust <$> maybeAuthPair , menuItemAccessCallback = isJust <$> maybeAuthPair

View File

@ -9,38 +9,38 @@
module Handler.Course where module Handler.Course where
import Import import Import
import Handler.Utils import Handler.Utils
-- import Data.Time -- import Data.Time
import qualified Data.Text as T import qualified Data.Text as T
import Data.Function ((&)) import Data.Function ((&))
import Yesod.Form.Bootstrap3 import Yesod.Form.Bootstrap3
import Colonnade hiding (fromMaybe) import Colonnade hiding (fromMaybe)
import Yesod.Colonnade import Yesod.Colonnade
import qualified Data.UUID.Cryptographic as UUID import qualified Data.UUID.Cryptographic as UUID
getCourseListR :: Handler TypedContent getCourseListR :: Handler TypedContent
getCourseListR = redirect TermShowR getCourseListR = redirect TermShowR
getCourseListTermR :: TermId -> Handler Html getCourseListTermR :: TermId -> Handler Html
getCourseListTermR tidini = do getCourseListTermR tidini = do
(term,courses) <- runDB $ (,) (term,courses) <- runDB $ (,)
<$> get tidini <$> get tidini
<*> selectList [CourseTermId ==. tidini] [Asc CourseShorthand] <*> selectList [CourseTermId ==. tidini] [Asc CourseShorthand]
when (isNothing term) $ do when (isNothing term) $ do
addMessage "warning" [shamlet| Semester #{toPathPiece tidini} nicht gefunden. |] addMessage "warning" [shamlet| Semester #{toPathPiece tidini} nicht gefunden. |]
redirect TermShowR redirect TermShowR
-- TODO: several runDBs per TableRow are probably too inefficient! -- TODO: several runDBs per TableRow are probably too inefficient!
let colonnadeTerms = mconcat let colonnadeTerms = mconcat
[ headed "Kürzel" $ (\ckv -> [ headed "Kürzel" $ (\ckv ->
let c = entityVal ckv let c = entityVal ckv
shd = courseShorthand c shd = courseShorthand c
tid = courseTermId c tid = courseTermId c
in [whamlet| <a href=@{CourseShowR tid shd}>#{shd} |] ) in [whamlet| <a href=@{CourseShowR tid shd}>#{shd} |] )
-- , headed "Institut" $ [shamlet| #{course} |] -- , headed "Institut" $ [shamlet| #{course} |]
, headed "Beginn Anmeldung" $ fromString.(maybe "" formatTimeGerWD).courseRegisterFrom.entityVal , headed "Beginn Anmeldung" $ fromString.(maybe "" formatTimeGerWD).courseRegisterFrom.entityVal
, headed "Ende Anmeldung" $ fromString.(maybe "" formatTimeGerWD).courseRegisterTo.entityVal , headed "Ende Anmeldung" $ fromString.(maybe "" formatTimeGerWD).courseRegisterTo.entityVal
@ -49,60 +49,60 @@ getCourseListTermR tidini = do
partiNum <- handlerToWidget $ runDB $ count [CourseParticipantCourseId ==. cid] partiNum <- handlerToWidget $ runDB $ count [CourseParticipantCourseId ==. cid]
[whamlet| #{show partiNum} |] [whamlet| #{show partiNum} |]
) )
, headed " " $ (\ckv -> , headed " " $ (\ckv ->
let c = entityVal ckv let c = entityVal ckv
shd = courseShorthand c shd = courseShorthand c
tid = courseTermId c tid = courseTermId c
in do in do
adminLink <- handlerToWidget $ isAuthorized (CourseEditExistR tid shd ) False adminLink <- handlerToWidget $ isAuthorized (CourseEditExistR tid shd ) False
-- if (adminLink==Authorized) then linkButton "Ändern" BCWarning (CourseEditExistR tid shd) else "" -- if (adminLink==Authorized) then linkButton "Ändern" BCWarning (CourseEditExistR tid shd) else ""
[whamlet| [whamlet|
$if adminLink == Authorized $if adminLink == Authorized
<a href=@{CourseEditExistR tid shd}> <a href=@{CourseEditExistR tid shd}>
editieren editieren
|] |]
) )
] ]
let pageLinks = let pageLinks =
[ NavbarLeft $ MenuItem [ NavbarLeft $ MenuItem
{ menuItemLabel = "Neuer Kurs" { menuItemLabel = "Neuer Kurs"
, menuItemRoute = CourseEditR , menuItemRoute = CourseEditR
, menuItemAccessCallback = (== Authorized) <$> isAuthorized CourseEditR False , menuItemAccessCallback = (== Authorized) <$> isAuthorized CourseEditR False
} }
] ]
let coursesTable = encodeWidgetTable tableSortable colonnadeTerms courses
defaultLinkLayout pageLinks $ do defaultLinkLayout pageLinks $ do
-- defaultLayout $ do -- defaultLayout $ do
setTitle "Semesterkurse" setTitle "Semesterkurse"
linkButton "Neuen Kurs anlegen" BCPrimary CourseEditR $(widgetFile "courses")
encodeWidgetTable tableDefault colonnadeTerms courses -- (map entityVal courses)
getCourseShowR :: TermId -> Text -> Handler Html getCourseShowR :: TermId -> Text -> Handler Html
getCourseShowR tid csh = do getCourseShowR tid csh = do
mbAid <- maybeAuthId mbAid <- maybeAuthId
(courseEnt,(schoolMB,participants,mbRegistered)) <- runDB $ do (courseEnt,(schoolMB,participants,mbRegistered)) <- runDB $ do
courseEnt@(Entity cid course) <- getBy404 $ CourseTermShort tid csh courseEnt@(Entity cid course) <- getBy404 $ CourseTermShort tid csh
dependent <- (,,) dependent <- (,,)
<$> get (courseSchoolId course) -- join <$> get (courseSchoolId course) -- join
<*> count [CourseParticipantCourseId ==. cid] -- join <*> count [CourseParticipantCourseId ==. cid] -- join
<*> (case mbAid of -- TODO: Someone please refactor this late-night mess here! <*> (case mbAid of -- TODO: Someone please refactor this late-night mess here!
Nothing -> return False Nothing -> return False
(Just aid) -> do (Just aid) -> do
regL <- getBy (UniqueCourseParticipant cid aid) regL <- getBy (UniqueCourseParticipant cid aid)
return $ isJust regL) return $ isJust regL)
return $ (courseEnt,dependent) return $ (courseEnt,dependent)
let course = entityVal courseEnt let course = entityVal courseEnt
(regWidget, regEnctype) <- generateFormPost $ identifyForm "registerBtn" $ registerButton $ mbRegistered (regWidget, regEnctype) <- generateFormPost $ identifyForm "registerBtn" $ registerButton $ mbRegistered
defaultLayout $ do defaultLayout $ do
setTitle $ [shamlet| #{toPathPiece tid} - #{csh}|] setTitle $ [shamlet| #{toPathPiece tid} - #{csh}|]
$(widgetFile "course") $(widgetFile "course")
registerButton :: Bool -> Form () registerButton :: Bool -> Form ()
registerButton registered = renderAForm FormStandard $ registerButton registered = renderAForm FormStandard $
pure () <* bootstrapSubmit regMsg pure () <* bootstrapSubmit regMsg
where where
msg = if registered then "Abmelden" else "Anmelden" msg = if registered then "Abmelden" else "Anmelden"
regMsg = msg :: BootstrapSubmit Text regMsg = msg :: BootstrapSubmit Text
postCourseShowR :: TermId -> Text -> Handler Html postCourseShowR :: TermId -> Text -> Handler Html
postCourseShowR tid csh = do postCourseShowR tid csh = do
aid <- requireAuthId aid <- requireAuthId
@ -110,30 +110,30 @@ postCourseShowR tid csh = do
(Entity cid _) <- getBy404 $ CourseTermShort tid csh (Entity cid _) <- getBy404 $ CourseTermShort tid csh
registered <- isJust <$> (getBy $ UniqueCourseParticipant cid aid) registered <- isJust <$> (getBy $ UniqueCourseParticipant cid aid)
return (cid, registered) return (cid, registered)
((regResult,_), _) <- runFormPost $ identifyForm "registerBtn" $ registerButton registered ((regResult,_), _) <- runFormPost $ identifyForm "registerBtn" $ registerButton registered
case regResult of case regResult of
(FormSuccess _) (FormSuccess _)
| registered -> do | registered -> do
runDB $ deleteBy $ UniqueCourseParticipant cid aid runDB $ deleteBy $ UniqueCourseParticipant cid aid
addMessage "info" "Sie wurden abgemeldet." addMessage "info" "Sie wurden abgemeldet."
| otherwise -> do | otherwise -> do
actTime <- liftIO $ getCurrentTime actTime <- liftIO $ getCurrentTime
regOk <- runDB $ insertUnique $ CourseParticipant cid aid actTime regOk <- runDB $ insertUnique $ CourseParticipant cid aid actTime
when (isJust regOk) $ addMessage "success" "Erfolgreich angemeldet!" when (isJust regOk) $ addMessage "success" "Erfolgreich angemeldet!"
(_other) -> return () -- TODO check this! (_other) -> return () -- TODO check this!
-- redirect or not?! I guess not, since we want GET now -- redirect or not?! I guess not, since we want GET now
getCourseShowR tid csh getCourseShowR tid csh
getCourseEditR :: Handler Html getCourseEditR :: Handler Html
getCourseEditR = do getCourseEditR = do
-- TODO: Defaults für Semester hier ermitteln und übergeben -- TODO: Defaults für Semester hier ermitteln und übergeben
courseEditHandler Nothing courseEditHandler Nothing
postCourseEditR :: Handler Html postCourseEditR :: Handler Html
postCourseEditR = courseEditHandler Nothing postCourseEditR = courseEditHandler Nothing
getCourseEditExistR :: TermId -> Text -> Handler Html getCourseEditExistR :: TermId -> Text -> Handler Html
getCourseEditExistR tid csh = do getCourseEditExistR tid csh = do
course <- runDB $ getBy $ CourseTermShort tid csh course <- runDB $ getBy $ CourseTermShort tid csh
courseEditHandler course courseEditHandler course
@ -143,28 +143,28 @@ getCourseEditExistIDR cID = do
courseID <- UUID.decrypt cIDKey cID courseID <- UUID.decrypt cIDKey cID
courseEditHandler =<< runDB (getEntity courseID) courseEditHandler =<< runDB (getEntity courseID)
courseEditHandler :: Maybe (Entity Course) -> Handler Html courseEditHandler :: Maybe (Entity Course) -> Handler Html
courseEditHandler course = do courseEditHandler course = do
aid <- requireAuthId aid <- requireAuthId
((result, formWidget), formEnctype) <- runFormPost $ newCourseForm $ courseToForm <$> course ((result, formWidget), formEnctype) <- runFormPost $ newCourseForm $ courseToForm <$> course
action <- lookupPostParam "formaction" action <- lookupPostParam "formaction"
case (result,action) of case (result,action) of
(FormSuccess res, fAct) (FormSuccess res, fAct)
| fAct == formActionDelete | fAct == formActionDelete
, Just cid <- cfCourseId res -> do , Just cid <- cfCourseId res -> do
runDB $ deleteCascade cid -- TODO Sicherheitsabfrage einbauen! runDB $ deleteCascade cid -- TODO Sicherheitsabfrage einbauen!
let cti = toPathPiece $ cfTerm res let cti = toPathPiece $ cfTerm res
addMessage "info" [shamlet| Kurs #{cti}/#{cfShort res} wurde gelöscht!|] addMessage "info" [shamlet| Kurs #{cti}/#{cfShort res} wurde gelöscht!|]
redirect $ CourseListTermR $ cfTerm res redirect $ CourseListTermR $ cfTerm res
| fAct == formActionSave | fAct == formActionSave
, Just cid <- cfCourseId res -> do , Just cid <- cfCourseId res -> do
let tid = cfTerm res let tid = cfTerm res
actTime <- liftIO getCurrentTime actTime <- liftIO getCurrentTime
updateokay <- runDB $ do updateokay <- runDB $ do
exists <- getBy $ CourseTermShort tid $ cfShort res exists <- getBy $ CourseTermShort tid $ cfShort res
let upokay = isNothing exists let upokay = isNothing exists
when upokay $ update cid when upokay $ update cid
[ CourseName =. cfName res [ CourseName =. cfName res
, CourseDescription =. cfDesc res , CourseDescription =. cfDesc res
, CourseLinkExternal =. cfLink res , CourseLinkExternal =. cfLink res
@ -179,17 +179,17 @@ courseEditHandler course = do
] ]
return upokay return upokay
let cti = toPathPiece $ cfTerm res let cti = toPathPiece $ cfTerm res
if updateokay if updateokay
then do then do
addMessage "info" [shamlet| Kurs #{cti}/#{cfShort res} wurde geändert. |] addMessage "info" [shamlet| Kurs #{cti}/#{cfShort res} wurde geändert. |]
redirect $ CourseListTermR $ cfTerm res redirect $ CourseListTermR $ cfTerm res
else do else do
addMessage "danger" [shamlet| Kurs #{cti}/#{cfShort res} konnte nicht geändert werden. addMessage "danger" [shamlet| Kurs #{cti}/#{cfShort res} konnte nicht geändert werden.
\ Es gibt bereits einen anderen Kurs mit diesem Kürzel in diesem Semester.|] \ Es gibt bereits einen anderen Kurs mit diesem Kürzel in diesem Semester.|]
| fAct == formActionSave | fAct == formActionSave
, Nothing <- cfCourseId res -> do , Nothing <- cfCourseId res -> do
actTime <- liftIO getCurrentTime actTime <- liftIO getCurrentTime
insertOkay <- runDB $ insertUnique $ Course insertOkay <- runDB $ insertUnique $ Course
{ courseName = cfName res { courseName = cfName res
, courseDescription = cfDesc res , courseDescription = cfDesc res
, courseLinkExternal = cfLink res , courseLinkExternal = cfLink res
@ -204,17 +204,17 @@ courseEditHandler course = do
, courseChanged = actTime , courseChanged = actTime
, courseCreatedBy = aid , courseCreatedBy = aid
, courseChangedBy = aid , courseChangedBy = aid
} }
case insertOkay of case insertOkay of
(Just cid) -> do (Just cid) -> do
runDB $ insert_ $ Lecturer aid cid runDB $ insert_ $ Lecturer aid cid
let cti = toPathPiece $ cfTerm res let cti = toPathPiece $ cfTerm res
addMessage "info" [shamlet|Kurs #{cti}/#{cfShort res} wurde angelegt.|] addMessage "info" [shamlet|Kurs #{cti}/#{cfShort res} wurde angelegt.|]
redirect $ CourseListTermR $ cfTerm res redirect $ CourseListTermR $ cfTerm res
Nothing -> do Nothing -> do
let cti = toPathPiece $ cfTerm res let cti = toPathPiece $ cfTerm res
addMessage "danger" [shamlet|Es gibt bereits einen Kurs #{cfShort res} in Semester #{cti}.|] addMessage "danger" [shamlet|Es gibt bereits einen Kurs #{cfShort res} in Semester #{cti}.|]
(FormFailure _,_) -> addMessage "warning" "Bitte Eingabe korrigieren." (FormFailure _,_) -> addMessage "warning" "Bitte Eingabe korrigieren."
_other -> return () _other -> return ()
let formTitle = "Kurs editieren/anlegen" :: Text let formTitle = "Kurs editieren/anlegen" :: Text
let actionUrl = CourseEditR let actionUrl = CourseEditR
@ -222,28 +222,28 @@ courseEditHandler course = do
defaultLayout $ do defaultLayout $ do
setTitle [shamlet| #{formTitle} |] setTitle [shamlet| #{formTitle} |]
$(widgetFile "formPage") $(widgetFile "formPage")
data CourseForm = CourseForm data CourseForm = CourseForm
{ cfCourseId :: Maybe CourseId -- Maybe CryptoUUIDCourse { cfCourseId :: Maybe CourseId -- Maybe CryptoUUIDCourse
, cfName :: Text , cfName :: Text
, cfDesc :: Maybe Html , cfDesc :: Maybe Html
, cfLink :: Maybe Text , cfLink :: Maybe Text
, cfShort :: Text , cfShort :: Text
, cfTerm :: TermId , cfTerm :: TermId
, cfSchool :: SchoolId , cfSchool :: SchoolId
, cfCapacity :: Maybe Int , cfCapacity :: Maybe Int
, cfHasReg :: Bool , cfHasReg :: Bool
, cfRegFrom :: Maybe UTCTime , cfRegFrom :: Maybe UTCTime
, cfRegTo :: Maybe UTCTime , cfRegTo :: Maybe UTCTime
} }
instance Show CourseForm where instance Show CourseForm where
show cf = T.unpack (cfShort cf) ++ ' ':(show $ cfCourseId cf) show cf = T.unpack (cfShort cf) ++ ' ':(show $ cfCourseId cf)
courseToForm :: Entity Course -> CourseForm courseToForm :: Entity Course -> CourseForm
courseToForm cEntity = CourseForm courseToForm cEntity = CourseForm
{ cfCourseId = Just $ entityKey cEntity { cfCourseId = Just $ entityKey cEntity
, cfName = courseName course , cfName = courseName course
, cfDesc = courseDescription course , cfDesc = courseDescription course
@ -253,26 +253,26 @@ courseToForm cEntity = CourseForm
, cfSchool = courseSchoolId course , cfSchool = courseSchoolId course
, cfCapacity = courseCapacity course , cfCapacity = courseCapacity course
, cfHasReg = courseHasRegistration course , cfHasReg = courseHasRegistration course
, cfRegFrom = courseRegisterFrom course , cfRegFrom = courseRegisterFrom course
, cfRegTo = courseRegisterTo course , cfRegTo = courseRegisterTo course
} }
where where
course = entityVal cEntity course = entityVal cEntity
newCourseForm :: Maybe CourseForm -> Form CourseForm newCourseForm :: Maybe CourseForm -> Form CourseForm
newCourseForm template = identForm FIDcourse $ \html -> do newCourseForm template = identForm FIDcourse $ \html -> do
-- mopt hiddenField -- mopt hiddenField
-- cidKey <- getsYesod appCryptoIDKey -- cidKey <- getsYesod appCryptoIDKey
-- courseId <- runMaybeT $ do -- courseId <- runMaybeT $ do
-- cid <- cfCourseId template -- cid <- cfCourseId template
-- UUID.encrypt cidKey cid -- UUID.encrypt cidKey cid
(result, widget) <- flip (renderAForm FormStandard) html $ CourseForm (result, widget) <- flip (renderAForm FormStandard) html $ CourseForm
-- <$> pure cid -- $ join $ cfCourseId <$> template -- why doesnt this work? -- <$> pure cid -- $ join $ cfCourseId <$> template -- why doesnt this work?
<$> aopt hiddenField "KursId" (cfCourseId <$> template) <$> aopt hiddenField "KursId" (cfCourseId <$> template)
<*> areq textField (fsb "Name") (cfName <$> template) <*> areq textField (fsb "Name") (cfName <$> template)
<*> aopt htmlField (fsb "Beschreibung") (cfDesc <$> template) <*> aopt htmlField (fsb "Beschreibung") (cfDesc <$> template)
<*> aopt urlField (fsb "Homepage") (cfLink <$> template) <*> aopt urlField (fsb "Homepage") (cfLink <$> template)
<*> areq textField (fsb "Kürzel" <*> areq textField (fsb "Kürzel"
-- & addAttr "disabled" "disabled" -- & addAttr "disabled" "disabled"
& setTooltip "Muss innerhalb des Semesters eindeutig sein") & setTooltip "Muss innerhalb des Semesters eindeutig sein")
(cfShort <$> template) (cfShort <$> template)
@ -282,9 +282,9 @@ newCourseForm template = identForm FIDcourse $ \html -> do
<*> areq checkBoxField (fsb "Anmeldung") (cfHasReg <$> template) <*> areq checkBoxField (fsb "Anmeldung") (cfHasReg <$> template)
<*> aopt utcTimeField (fsb "Anmeldung von:") (cfRegFrom <$> template) <*> aopt utcTimeField (fsb "Anmeldung von:") (cfRegFrom <$> template)
<*> aopt utcTimeField (fsb "Anmeldung bis:") (cfRegTo <$> template) <*> aopt utcTimeField (fsb "Anmeldung bis:") (cfRegTo <$> template)
-- <* bootstrapSubmit (bsSubmit (show cid)) -- <* bootstrapSubmit (bsSubmit (show cid))
return $ case result of return $ case result of
FormSuccess courseResult FormSuccess courseResult
| errorMsgs <- validateCourse courseResult | errorMsgs <- validateCourse courseResult
, not $ null errorMsgs -> , not $ null errorMsgs ->
(FormFailure errorMsgs, (FormFailure errorMsgs,
@ -293,18 +293,18 @@ newCourseForm template = identForm FIDcourse $ \html -> do
<h4> Fehler: <h4> Fehler:
<ul> <ul>
$forall errmsg <- errorMsgs $forall errmsg <- errorMsgs
<li> #{errmsg} <li> #{errmsg}
^{widget} ^{widget}
|] |]
) )
_ -> (result, widget) _ -> (result, widget)
-- where -- where
-- cid :: Maybe CourseId -- cid :: Maybe CourseId
-- cid = join $ cfCourseId <$> template -- cid = join $ cfCourseId <$> template
validateCourse :: CourseForm -> [Text] validateCourse :: CourseForm -> [Text]
validateCourse (CourseForm{..}) = validateCourse (CourseForm{..}) =
[ msg | (False, msg) <- [ msg | (False, msg) <-
[ [
( cfRegFrom <= cfRegTo ( cfRegFrom <= cfRegTo
@ -324,5 +324,3 @@ validateCourse (CourseForm{..}) =
, "Anmeldungen aktivieren oder Anmeldezeitraum löschen" , "Anmeldungen aktivieren oder Anmeldezeitraum löschen"
) )
] ] ] ]

View File

@ -9,11 +9,11 @@
module Handler.Term where module Handler.Term where
import Import import Import
import Handler.Utils import Handler.Utils
import qualified Data.Text as T import qualified Data.Text as T
import Yesod.Form.Bootstrap3 import Yesod.Form.Bootstrap3
import Colonnade hiding (bool) import Colonnade hiding (bool)
import Yesod.Colonnade import Yesod.Colonnade
@ -27,74 +27,74 @@ getTermShowR = do
-- term <- runDB $ E.select . E.from $ \(term) -> do -- term <- runDB $ E.select . E.from $ \(term) -> do
-- E.orderBy [E.desc $ term E.^. TermStart ] -- E.orderBy [E.desc $ term E.^. TermStart ]
-- return term -- return term
-- --
termData <- runDB $ E.select . E.from $ \term -> do termData <- runDB $ E.select . E.from $ \term -> do
E.orderBy [E.desc $ term E.^. TermStart ] E.orderBy [E.desc $ term E.^. TermStart ]
let courseCount :: E.SqlExpr (E.Value Int) let courseCount :: E.SqlExpr (E.Value Int)
courseCount = E.sub_select . E.from $ \course -> do courseCount = E.sub_select . E.from $ \course -> do
E.where_ $ term E.^. TermId E.==. course E.^. CourseTermId E.where_ $ term E.^. TermId E.==. course E.^. CourseTermId
return E.countRows return E.countRows
return (term, courseCount) return (term, courseCount)
selectRep $ do selectRep $ do
provideRep $ return $ toJSON $ map fst termData provideRep $ return $ toJSON $ map fst termData
provideRep $ do provideRep $ do
let colonnadeTerms = mconcat let colonnadeTerms = mconcat
[ headed "Kürzel" $ \(Entity tid Term{..},_) -> do [ headed "Kürzel" $ \(Entity tid Term{..},_) -> do
-- Scrap this if to slow, create term edit page instead -- Scrap this if to slow, create term edit page instead
adminLink <- handlerToWidget $ isAuthorized (TermEditExistR tid) False adminLink <- handlerToWidget $ isAuthorized (TermEditExistR tid) False
[whamlet| [whamlet|
$if adminLink == Authorized $if adminLink == Authorized
<a href=@{TermEditExistR tid}> <a href=@{TermEditExistR tid}>
#{termToText termName} #{termToText termName}
$else $else
#{termToText termName} #{termToText termName}
|] |]
, headed "Beginn Vorlesungen" $ \(Entity _ Term{..},_) -> , headed "Beginn Vorlesungen" $ \(Entity _ Term{..},_) ->
fromString $ formatTimeGerWD termLectureStart fromString $ formatTimeGerWD termLectureStart
, headed "Ende Vorlesungen" $ \(Entity _ Term{..},_) -> , headed "Ende Vorlesungen" $ \(Entity _ Term{..},_) ->
fromString $ formatTimeGerWD termLectureEnd fromString $ formatTimeGerWD termLectureEnd
, headed "Aktiv" $ \(Entity _ Term{..},_) -> , headed "Aktiv" $ \(Entity _ Term{..},_) ->
bool "" tickmark termActive bool "" tickmark termActive
, headed "Kursliste" $ \(Entity tid Term{..}, E.Value numCourses) -> , headed "Kursliste" $ \(Entity tid Term{..}, E.Value numCourses) ->
[whamlet| [whamlet|
<a href=@{CourseListTermR tid}> <a href=@{CourseListTermR tid}>
#{show numCourses} Kurse #{show numCourses} Kurse
|] |]
, headed "Semesteranfang" $ \(Entity _ Term{..},_) -> , headed "Semesteranfang" $ \(Entity _ Term{..},_) ->
fromString $ formatTimeGerWD termStart fromString $ formatTimeGerWD termStart
, headed "Semesterende" $ \(Entity _ Term{..},_) -> , headed "Semesterende" $ \(Entity _ Term{..},_) ->
fromString $ formatTimeGerWD termEnd fromString $ formatTimeGerWD termEnd
, headed "Feiertage im Semester" $ \(Entity _ Term{..},_) -> , headed "Feiertage im Semester" $ \(Entity _ Term{..},_) ->
fromString $ (intercalate ", ") $ map formatTimeGerWD termHolidays fromString $ (intercalate ", ") $ map formatTimeGerWD termHolidays
] ]
defaultLayout $ do defaultLayout $ do
setTitle "Freigeschaltete Semester" setTitle "Freigeschaltete Semester"
encodeWidgetTable tableDefault colonnadeTerms termData encodeWidgetTable tableSortable colonnadeTerms termData
getTermEditR :: Handler Html getTermEditR :: Handler Html
getTermEditR = do getTermEditR = do
-- TODO: Defaults für Semester hier ermitteln und übergeben -- TODO: Defaults für Semester hier ermitteln und übergeben
termEditHandler Nothing termEditHandler Nothing
postTermEditR :: Handler Html postTermEditR :: Handler Html
postTermEditR = termEditHandler Nothing postTermEditR = termEditHandler Nothing
getTermEditExistR :: TermId -> Handler Html getTermEditExistR :: TermId -> Handler Html
getTermEditExistR tid = do getTermEditExistR tid = do
term <- runDB $ get tid term <- runDB $ get tid
termEditHandler term termEditHandler term
termEditHandler :: Maybe Term -> Handler Html termEditHandler :: Maybe Term -> Handler Html
termEditHandler term = do termEditHandler term = do
((result, formWidget), formEnctype) <- runFormPost $ newTermForm term ((result, formWidget), formEnctype) <- runFormPost $ newTermForm term
case result of case result of
(FormSuccess res) -> do (FormSuccess res) -> do
-- term <- runDB $ get $ TermKey termName -- term <- runDB $ get $ TermKey termName
runDB $ repsert (TermKey $ termName res) res runDB $ repsert (TermKey $ termName res) res
let tid = termToText $ termName res let tid = termToText $ termName res
let msg = "Semester " `T.append` tid `T.append` " erfolgreich editiert." let msg = "Semester " `T.append` tid `T.append` " erfolgreich editiert."
addMessage "success" [shamlet| #{msg} |] addMessage "success" [shamlet| #{msg} |]
redirect TermShowR redirect TermShowR
(FormMissing ) -> return () (FormMissing ) -> return ()
@ -104,7 +104,7 @@ termEditHandler term = do
defaultLayout $ do defaultLayout $ do
setTitle [shamlet| #{formTitle} |] setTitle [shamlet| #{formTitle} |]
$(widgetFile "formPage") $(widgetFile "formPage")
newTermForm :: Maybe Term -> Form Term newTermForm :: Maybe Term -> Form Term
newTermForm template html = do newTermForm template html = do
(result, widget) <- flip (renderAForm FormStandard) html $ Term (result, widget) <- flip (renderAForm FormStandard) html $ Term
@ -116,8 +116,8 @@ newTermForm template html = do
<*> areq dayField (bfs ("Ende Vorlesungen" :: Text)) (termLectureEnd <$> template) <*> areq dayField (bfs ("Ende Vorlesungen" :: Text)) (termLectureEnd <$> template)
<*> areq checkBoxField (bfs ("Aktiv" :: Text)) (termActive <$> template) <*> areq checkBoxField (bfs ("Aktiv" :: Text)) (termActive <$> template)
<* submitButton <* submitButton
return $ case result of return $ case result of
FormSuccess termResult FormSuccess termResult
| errorMsgs <- validateTerm termResult | errorMsgs <- validateTerm termResult
, not $ null errorMsgs -> , not $ null errorMsgs ->
(FormFailure errorMsgs, (FormFailure errorMsgs,
@ -126,13 +126,13 @@ newTermForm template html = do
<h4> Fehler: <h4> Fehler:
<ul> <ul>
$forall errmsg <- errorMsgs $forall errmsg <- errorMsgs
<li> #{errmsg} <li> #{errmsg}
^{widget} ^{widget}
|] |]
) )
_ -> (result, widget) _ -> (result, widget)
{- {-
where where
set :: Text -> FieldSettings site set :: Text -> FieldSettings site
set = bfs set = bfs
-} -}

View File

@ -41,5 +41,5 @@ getUsersR = do
-- ++ map (\school -> headed (text2widget $ schoolName $ entityVal school) (\u -> "xx")) schools -- ++ map (\school -> headed (text2widget $ schoolName $ entityVal school) (\u -> "xx")) schools
defaultLayout $ do defaultLayout $ do
setTitle "Comprehensive User List" setTitle "Comprehensive User List"
let userList = encodeWidgetTable tableDefault colonnadeUsers users let userList = encodeWidgetTable tableSortable colonnadeUsers users
$(widgetFile "users") $(widgetFile "users")

View File

@ -23,12 +23,15 @@ import Data.Either
-- Table design -- Table design
tableDefault :: Attribute tableDefault :: Attribute
tableDefault = customAttribute "class" "table table-striped table-hover" tableDefault = customAttribute "class" "table table-striped table-hover"
tableSortable :: Attribute
tableSortable = customAttribute "class" "js-sortable"
-- Colonnade Tools -- Colonnade Tools
numberColonnade :: (IsString c) => Colonnade Headed Int c numberColonnade :: (IsString c) => Colonnade Headed Int c
numberColonnade = headed "Nr" (fromString.show) numberColonnade = headed "Nr" (fromString.show)
pairColonnade :: (Functor h) => Colonnade h a c -> Colonnade h b c -> Colonnade h (a,b) c pairColonnade :: (Functor h) => Colonnade h a c -> Colonnade h b c -> Colonnade h (a,b) c
pairColonnade a b = mconcat [ lmap fst a, lmap snd b] pairColonnade a b = mconcat [ lmap fst a, lmap snd b]
@ -39,8 +42,8 @@ encodeHeadedWidgetTableNumbered attrs colo tdata =
encodeWidgetTable attrs (mconcat [numberCol, lmap snd colo]) (zip [1..] tdata) encodeWidgetTable attrs (mconcat [numberCol, lmap snd colo]) (zip [1..] tdata)
where where
numberCol :: Colonnade Headed (Int,a) (WidgetT site IO ()) numberCol :: Colonnade Headed (Int,a) (WidgetT site IO ())
numberCol = headed "Nr" (fromString.show.fst) numberCol = headed "Nr" (fromString.show.fst)
headedRowSelector :: ( PathPiece b headedRowSelector :: ( PathPiece b
, Eq b , Eq b
) )
@ -74,7 +77,7 @@ headedRowSelector toExternal fromExternal attrs colonnade tdata = do
selectionIdent <- newFormIdent selectionIdent <- newFormIdent
(selectionResults, selectionBoxes) <- fmap unzip . forM externalIds $ \ident -> mopt (checkbox ident) ("" { fsName = Just selectionIdent }) Nothing (selectionResults, selectionBoxes) <- fmap unzip . forM externalIds $ \ident -> mopt (checkbox ident) ("" { fsName = Just selectionIdent }) Nothing
let let
selColonnade :: Colonnade Headed Int (Cell UniWorX) selColonnade :: Colonnade Headed Int (Cell UniWorX)
selColonnade = headed "Markiert" $ cell . fvInput . (selectionBoxes !!) selColonnade = headed "Markiert" $ cell . fvInput . (selectionBoxes !!)

6
templates/courses.hamlet Normal file
View File

@ -0,0 +1,6 @@
<div>
<h1>Kursübersicht für Semester #{termToText $ unTermKey tidini}
^{coursesTable}
<div>
<a href=@{CourseEditR}>Neuen Kurs anlegen

View File

@ -19,7 +19,3 @@
<!-- actual content --> <!-- actual content -->
^{widget} ^{widget}
<!-- footer -->
<footer>
#{appCopyright $ appSettings master}

View File

@ -28,12 +28,14 @@
--blackbase: #1A2A36; --blackbase: #1A2A36;
--fontbase: #34303a; --fontbase: #34303a;
--fontsec: #5b5861; --fontsec: #5b5861;
--primarybase: #4C7A9C;
/* THEME INDEPENDENT COLORS */ /* THEME INDEPENDENT COLORS */
--errorbase: red; --errorbase: red;
--warningbase: #fe7700; --warningbase: #fe7700;
--validbase: #2dcc35; --validbase: #2dcc35;
--infobase: var(--darkbase);
/* FONTS */ /* FONTS */
@ -100,12 +102,23 @@ h4 {
font-size: 16px; font-size: 16px;
margin: 0; margin: 0;
} }
table {
margin: 21px 0;
/*width: 100%;*/
}
th, td { th, td {
text-align: left; text-align: left;
padding: 0 2px 0 4px; padding: 0 13px 0 7px;
vertical-align: baseline;
}
th:first-child,
td:first-child {
padding-left: 0;
border-left: 0;
}
th {
border-left: 2px solid var(--greybase);
} }
/* LAYOUT */ /* LAYOUT */
.main { .main {
display: flex; display: flex;
@ -115,6 +128,7 @@ th, td {
.main__aside { .main__aside {
width: 300px; width: 300px;
flex-shrink: 0;
padding-right: 20px; padding-right: 20px;
padding-left: 4vw; padding-left: 4vw;
background-color: var(--darkbase); background-color: var(--darkbase);
@ -144,3 +158,57 @@ th, td {
outline: 5px auto var(--lightbase); outline: 5px auto var(--lightbase);
outline: 5px auto -webkit-focus-ring-color; outline: 5px auto -webkit-focus-ring-color;
} }
/* GENERAL BUTTON STYLES */
input[type="submit"],
input[type="button"],
button,
.btn {
outline: 0;
border: 0;
box-shadow: 0;
background-color: var(--lightbase);
color: white;
padding: 10px 17px;
min-width: 100px;
transition: all .1s;
font-size: 16px;
cursor: pointer;
border-radius: 4px;
display: inline-block;
}
input.btn-primary,
button.btn-primary,
.btn.btn-primary {
background-color: var(--primarybase);
}
input.btn-info,
button.btn-info,
.btn.btn-info {
background-color: var(--infobase)
}
input[type="submit"][disabled],
input[type="button"][disabled],
button[disabled],
.btn[disabled] {
opacity: 0.3;
background-color: var(--greybase);
cursor: default;
}
input[type="submit"]:not([disabled]):hover,
input[type="button"]:not([disabled]):hover,
button:not([disabled]):hover,
.btn:not([disabled]):hover {
background-color: var(--lighterbase);
text-decoration: underline;
}
input[type="submit"].btn-info:hover,
input[type="button"].btn-info:hover,
button.btn-info:hover,
.btn.btn-info:hover {
background-color: var(--greybase)
}

View File

@ -27,7 +27,7 @@
<a href=@{SubmissionListR}>Dateien hochladen und abrufen <a href=@{SubmissionListR}>Dateien hochladen und abrufen
<hr> <hr>
<div .js-show-hide.js-show-hide--collapsed> <div .js-show-hide data-collapsed=true>
<h2 .js-show-hide__toggle>Tabellen <h2 .js-show-hide__toggle>Tabellen
<table .js-sortable> <table .js-sortable>
<thead> <thead>

View File

@ -16,12 +16,31 @@ document.addEventListener('DOMContentLoaded', function() {
var toggle = toggles[el.dataset.index]; var toggle = toggles[el.dataset.index];
toggle.collapsed = !toggle.collapsed; toggle.collapsed = !toggle.collapsed;
toggle.parent.classList.toggle('js-show-hide--collapsed', toggle.collapsed); toggle.parent.classList.toggle('js-show-hide--collapsed', toggle.collapsed);
updateLocalStorage();
}); });
} }
function updateLocalStorage(id) {
let jsonToggles = JSON.stringify(toggles.map(t => {
return {id: t.index, collapsed: t.collapsed};
}));
window.localStorage.setItem('showHidesToggles', jsonToggles);
}
function collapsedStateInLocalStorage(id, fallBack) {
let lsData = JSON.parse(window.localStorage.getItem('showHidesToggles'));
if (lsData[id]) {
return lsData[id].collapsed;
}
return fallBack;
}
elements.forEach(function(el, i) { elements.forEach(function(el, i) {
el.dataset.index = i; el.dataset.index = i;
var coll = el.parentElement.classList.contains('js-show-hide--collapsed'); var coll = collapsedStateInLocalStorage(i, el.parentElement.dataset.collapsed === 'true');
if (coll) {
el.parentElement.classList.add('js-show-hide--collapsed')
}
Array.from(el.parentElement.children).forEach(function(el) { Array.from(el.parentElement.children).forEach(function(el) {
if (!el.classList.contains('js-show-hide__toggle')) { if (!el.classList.contains('js-show-hide__toggle')) {
el.classList.add('js-show-hide__target'); el.classList.add('js-show-hide__target');

View File

@ -13,8 +13,8 @@ table.js-sortable th.sorted-asc::after,
table.js-sortable th.sorted-desc::after { table.js-sortable th.sorted-desc::after {
content: ''; content: '';
position: absolute; position: absolute;
left: 0; right: 0;
top: 0; top: 15px;
width: 0; width: 0;
height: 0; height: 0;
transform: translateY(-100%); transform: translateY(-100%);

View File

@ -1,6 +1,5 @@
<div .asidenav> <div .asidenav>
<div .asidenav__box> <div .asidenav__box>
<h3 .asidenav__box-title>NavbarLefts
<ul .asidenav__list> <ul .asidenav__list>
$forall menuType <- menuTypes $forall menuType <- menuTypes
$case menuType $case menuType
@ -8,6 +7,7 @@
<li .asidenav__list-item :Just route == mcurrentRoute:.asidenav__list-item--active> <li .asidenav__list-item :Just route == mcurrentRoute:.asidenav__list-item--active>
<a .asidenav__link href=@{route}>#{label} <a .asidenav__link href=@{route}>#{label}
$of _ $of _
<div .asidenav__box> <div .asidenav__box>
<h3 .asidenav__box-title>WiSe 17/18 <h3 .asidenav__box-title>WiSe 17/18
<ul .asidenav__list> <ul .asidenav__list>

View File

@ -134,7 +134,7 @@
var nextInput = document.createElement('input'); var nextInput = document.createElement('input');
var remover = document.createElement('div'); var remover = document.createElement('div');
cont.classList.add('file-input__container'); cont.classList.add('file-input__container');
desc.classList.add('file-input__label'); desc.classList.add('file-input__label', 'btn');
remover.classList.add('file-input__remover'); remover.classList.add('file-input__remover');
nextInput.setAttribute('name', name); nextInput.setAttribute('name', name);
nextInput.setAttribute('type', 'file'); nextInput.setAttribute('type', 'file');

View File

@ -1,6 +1,6 @@
/* GENERAL STYLES FOR FORMS */ /* GENERAL STYLES FOR FORMS */
/* FORMS */ /* TEXT INPUTS */
input[type="text"], input[type="text"],
input[type="password"], input[type="password"],
input[type="url"], input[type="url"],
@ -27,36 +27,9 @@ input[type="email"]:focus {
background-color: transparent; background-color: transparent;
} }
input[type="submit"], /* BUTTON STYLE SEE default-layout.lucius */
input[type="button"],
button {
outline: 0;
border: 0;
box-shadow: 0;
background-color: var(--lightbase);
color: white;
padding: 10px 17px;
min-width: 100px;
transition: all .1s;
font-size: 16px;
cursor: pointer;
border-radius: 4px;
}
input[type="submit"][disabled],
input[type="button"][disabled],
button[disabled] {
opacity: 0.3;
background-color: var(--greybase);
cursor: default;
}
input[type="submit"]:not([disabled]):hover,
input[type="button"]:not([disabled]):hover,
button:not([disabled]):hover {
background-color: var(--lighterbase);
}
/* TEXTAREAS */
textarea { textarea {
outline: 0; outline: 0;
border: 0; border: 0;
@ -75,7 +48,7 @@ textarea:focus {
background-color: transparent; background-color: transparent;
border-bottom-color: var(--lightbase); border-bottom-color: var(--lightbase);
} }
/* FORM GROUPS */
.form-group { .form-group {
position: relative; position: relative;
display: grid; display: grid;
@ -267,12 +240,13 @@ input[type="file"] {
cursor: pointer; cursor: pointer;
} }
.file-input__label { .file-input__label {
background-color: var(--lighterbase);
text-align: left; text-align: left;
position: relative; position: relative;
min-width: 40px;
height: 30px; height: 30px;
} }
.file-input__label.btn {
padding: 5px 13px;
}
.file-input__label::after, .file-input__label::after,
.file-input__label::before { .file-input__label::before {
position: absolute; position: absolute;

View File

@ -3,12 +3,12 @@
<ul .navbar__list.list--inline> <ul .navbar__list.list--inline>
$forall menuType <- menuTypes $forall menuType <- menuTypes
$case menuType $case menuType
$of NavbarLeft (MenuItem label route _)
<li .navbar__list-item :Just route == mcurrentRoute:.navbar__list-item--active>
<a .navbar__link href=@{route}>#{label}
$of NavbarRight (MenuItem label route _) $of NavbarRight (MenuItem label route _)
<li .navbar__list-item :Just route == mcurrentRoute:.navbar__list-item--active> <li .navbar__list-item :Just route == mcurrentRoute:.navbar__list-item--active>
<a .navbar__link href=@{route}>#{label} <a .navbar__link href=@{route}>#{label}
$of NavbarSecondary (MenuItem label route _)
<li .navbar__list-item.navbar__list-item--secondary :Just route == mcurrentRoute:.navbar__list-item--active>
<a .navbar__link href=@{route}>#{label}
$of _ $of _
<div .navbar__pushdown> <div .navbar__pushdown>

View File

@ -25,12 +25,22 @@
text-transform: uppercase; text-transform: uppercase;
} }
.navbar__list-item--secondary {
margin-left: 20px;
background-color: var(--fontsec);
}
.navbar__list-item--secondary + .navbar__list-item--secondary {
margin-left: 0;
border-left: 0;
}
.navbar__list-item--active > .navbar__link { .navbar__list-item--active > .navbar__link {
background-color: white; background-color: white;
color: var(--darkbase); color: var(--darkbase);
} }
.navbar .navbar__list-item > .navbar__link:hover { .navbar .navbar__list-item > .navbar__link:hover {
color: var(--whitebase);
background-color: var(--darkbase); background-color: var(--darkbase);
} }