From b42e93e8918b0c3500ecdfd3e27fa5ecc18548cc Mon Sep 17 00:00:00 2001 From: Steffen Jost Date: Tue, 3 Dec 2024 11:56:48 +0100 Subject: [PATCH] chore(daily): implement left-over todos and i18n --- .../uniworx/categories/avs/de-de-formal.msg | 13 +- messages/uniworx/categories/avs/en-eu.msg | 7 +- .../courses/tutorial/de-de-formal.msg | 5 +- .../categories/courses/tutorial/en-eu.msg | 5 +- messages/uniworx/misc/de-de-formal.msg | 3 +- messages/uniworx/misc/en-eu.msg | 3 +- .../uniworx/utils/buttons/de-de-formal.msg | 5 +- messages/uniworx/utils/buttons/en-eu.msg | 5 +- src/Handler/School/DayTasks.hs | 145 ++++++++++-------- src/Utils.hs | 1 + 10 files changed, 109 insertions(+), 83 deletions(-) diff --git a/messages/uniworx/categories/avs/de-de-formal.msg b/messages/uniworx/categories/avs/de-de-formal.msg index fd790cef2..c20485fc6 100644 --- a/messages/uniworx/categories/avs/de-de-formal.msg +++ b/messages/uniworx/categories/avs/de-de-formal.msg @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Steffen Jost +# SPDX-FileCopyrightText: 2022-24 Steffen Jost # # SPDX-License-Identifier: AGPL-3.0-or-later AvsPersonInfo: AVS Personendaten @@ -54,10 +54,13 @@ AvsUserUnassociated user@UserDisplayName: AVS Id unbekannt für Nutzer #{user} AvsUserUnknownByAvs api@AvsPersonId: AVS kennt Id #{tshow api} nicht (mehr) AvsUserAmbiguous api@AvsPersonId: AVS Id #{tshow api} ist nicht eindeutig AvsStatusSearchEmpty: AVS lieferte keine Ausweisinformationen -AvsPersonSearchEmpty: AVS Suche lieferte leeres Ergebnis -AvsPersonSearchAmbiguous: AVS Suche lieferte mehrere uneindeutige Ergebnisse +AvsPersonSearchEmpty: Suche im AVS lieferte kein Ergebnis +AvsPersonSearchAmbiguous: Suche im AVS lieferte mehrere uneindeutige Ergebnisse AvsSetLicencesFailed reason@Text: Setzen der Fahrlizenz im AVS fehlgeschlagen. Grund: #{reason} AvsIdMismatch api1@AvsPersonId api2@AvsPersonId: AVS Suche für Id #{tshow api1} lieferte stattdessen Id #{tshow api2} AvsUserCreationFailed api@AvsPersonId: Für AVS Id #{tshow api} konnte kein neuer Benutzer angelegt werden, da es eine gemeinsame Id (z.B. Personalnummer) mit einem existierenden, aber verschiedenen Nutzer gibt. -AvsCardsEmpty: AVS Suche lieferte keinerlei Ausweiskarten -AvsCurrentData: Alle angezeigte Daten wurden kürzlich direkt über die AVS Schnittstelle abgerufen. \ No newline at end of file +AvsCardsEmpty: Suche im AVS lieferte keinerlei Ausweiskarten +AvsCurrentData: Alle angezeigte Daten wurden kürzlich direkt über die AVS Schnittstelle abgerufen. + +AvsNoApronCard: Kein gültiger Ausweis mit Vorfeld-Zugang vorhanden +AvsNoCompanyCard mcn@(Maybe CompanyName): Für buchende Firma #{maybeEmpty mcn ciOriginal} liegt kein gültiger Ausweis vor diff --git a/messages/uniworx/categories/avs/en-eu.msg b/messages/uniworx/categories/avs/en-eu.msg index 787d38a16..04cfa7397 100644 --- a/messages/uniworx/categories/avs/en-eu.msg +++ b/messages/uniworx/categories/avs/en-eu.msg @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Steffen Jost +# SPDX-FileCopyrightText: 2022-24 Steffen Jost # # SPDX-License-Identifier: AGPL-3.0-or-later AvsPersonInfo: AVS person info @@ -61,4 +61,7 @@ AvsSetLicencesFailed reason: Set driving licence within AVS failed. Reason: #{re AvsIdMismatch api1 api2: AVS search for id #{tshow api1} returned id #{tshow api2} instead AvsUserCreationFailed api@AvsPersonId: No new user could be created for AVS Id #{tshow api}, since an existing user shares at least one id presumed as unique AvsCardsEmpty: AVS search returned no id cards -AvsCurrentData: All shown data has been recently received via the AVS interface. \ No newline at end of file +AvsCurrentData: All shown data has been recently received via the AVS interface. + +AvsNoApronCard: No valid card granting apron access found +AvsNoCompanyCard mcn@(Maybe CompanyName): No valid card for booking company #{maybeEmpty mcn ciOriginal} found diff --git a/messages/uniworx/categories/courses/tutorial/de-de-formal.msg b/messages/uniworx/categories/courses/tutorial/de-de-formal.msg index 539f014f0..51d63a9cf 100644 --- a/messages/uniworx/categories/courses/tutorial/de-de-formal.msg +++ b/messages/uniworx/categories/courses/tutorial/de-de-formal.msg @@ -56,4 +56,7 @@ TutorialEyeExam: Sehtest TutorialNote: Kursnotiz TutorialDayAttendance day@Text: Anwesenheit #{day} TutorialDayNote day@Text: Anwesenheitsnotiz #{day} -TutorialParticipantsDayEdits day@Text: Kursteilnehmer-Tagesnotizen aktualisiert für #{day} \ No newline at end of file +TutorialParticipantsDayEdits day@Text: Kursteilnehmer-Tagesnotizen aktualisiert für #{day} + +CheckEyePermitMissing: Sehtest oder Führerschein fehlen noch +CheckEyePermitIncompatible: Sehtest und Führerschein passen nicht zusammen \ No newline at end of file diff --git a/messages/uniworx/categories/courses/tutorial/en-eu.msg b/messages/uniworx/categories/courses/tutorial/en-eu.msg index 465b37b9e..96c044f5d 100644 --- a/messages/uniworx/categories/courses/tutorial/en-eu.msg +++ b/messages/uniworx/categories/courses/tutorial/en-eu.msg @@ -57,4 +57,7 @@ TutorialEyeExam: Eye exam TutorialNote: Course note TutorialDayAttendance day: Attendance #{day} TutorialDayNote day: Attendance note #{day} -TutorialParticipantsDayEdits day: course participant day notes updated for #{day} \ No newline at end of file +TutorialParticipantsDayEdits day: course participant day notes updated for #{day} + +CheckEyePermitMissing: Eye exam or driving permit missing +CheckEyePermitIncompatible: Eye exam and driving permit are incompatible \ No newline at end of file diff --git a/messages/uniworx/misc/de-de-formal.msg b/messages/uniworx/misc/de-de-formal.msg index f7cf7a561..fbc36aa90 100644 --- a/messages/uniworx/misc/de-de-formal.msg +++ b/messages/uniworx/misc/de-de-formal.msg @@ -31,4 +31,5 @@ PaginationPage: Angzeigte Seite PaginationError: Paginierung Parameter dürfen nicht negativ sein NullDeletes: Zum Löschen NULL eingeben. -SortPriority: Sortierungspriorität \ No newline at end of file +SortPriority: Sortierungspriorität +NoProblem: Keine Probleme gefunden \ No newline at end of file diff --git a/messages/uniworx/misc/en-eu.msg b/messages/uniworx/misc/en-eu.msg index da5d1efab..05d12945c 100644 --- a/messages/uniworx/misc/en-eu.msg +++ b/messages/uniworx/misc/en-eu.msg @@ -31,4 +31,5 @@ PaginationPage: Page to show PaginationError: Pagination parameter must not be negative NullDeletes: Enter NULL to delete. -SortPriority: Sort order priority \ No newline at end of file +SortPriority: Sort order priority +NoProblem: No Probleme found \ No newline at end of file diff --git a/messages/uniworx/utils/buttons/de-de-formal.msg b/messages/uniworx/utils/buttons/de-de-formal.msg index 8252a3a1c..7c999bd06 100644 --- a/messages/uniworx/utils/buttons/de-de-formal.msg +++ b/messages/uniworx/utils/buttons/de-de-formal.msg @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Gregor Kleen ,Steffen Jost ,Winnie Ros ,Sarah Vaupel +# SPDX-FileCopyrightText: 2022-24 Gregor Kleen ,Steffen Jost ,Winnie Ros ,Sarah Vaupel ,Steffen Jost # # SPDX-License-Identifier: AGPL-3.0-or-later @@ -57,4 +57,5 @@ BtnFinishExam: Prüfungsergebnisse sichtbar schalten BtnConfirm: Bestätigen BtnCourseRegisterAdd: Personen suchen BtnCourseRegisterConfirm: Ausgewählte Personen anmelden -BtnCourseRegisterAbort: Abbrechen \ No newline at end of file +BtnCourseRegisterAbort: Abbrechen +BtnCloseReload: Schließen und aktualisieren \ No newline at end of file diff --git a/messages/uniworx/utils/buttons/en-eu.msg b/messages/uniworx/utils/buttons/en-eu.msg index a83a7b3aa..4d5924f08 100644 --- a/messages/uniworx/utils/buttons/en-eu.msg +++ b/messages/uniworx/utils/buttons/en-eu.msg @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Steffen Jost ,Winnie Ros ,Sarah Vaupel +# SPDX-FileCopyrightText: 2022-24 Steffen Jost ,Winnie Ros ,Sarah Vaupel ,Steffen Jost # # SPDX-License-Identifier: AGPL-3.0-or-later @@ -57,4 +57,5 @@ BtnFinishExam: Make results visible BtnConfirm: Confirm BtnCourseRegisterAdd: Search persons BtnCourseRegisterConfirm: Register selected persons -BtnCourseRegisterAbort: Abort \ No newline at end of file +BtnCourseRegisterAbort: Abort +BtnCloseReload: Close and reload \ No newline at end of file diff --git a/src/Handler/School/DayTasks.hs b/src/Handler/School/DayTasks.hs index 295dc703a..a0433f6f3 100644 --- a/src/Handler/School/DayTasks.hs +++ b/src/Handler/School/DayTasks.hs @@ -4,8 +4,7 @@ -- SPDX-License-Identifier: AGPL-3.0-or-later {-# OPTIONS_GHC -fno-warn-orphans #-} -{-# OPTIONS_GHC -fno-warn-unused-top-binds #-} -- TODO during development only -{-# OPTIONS_GHC -fno-warn-unused-imports #-} -- TODO during development only +{-# OPTIONS_GHC -fno-warn-unused-top-binds #-} module Handler.School.DayTasks ( getSchoolDayR, postSchoolDayR @@ -16,7 +15,8 @@ import Import import Handler.Utils import Handler.Utils.Company -import Handler.Utils.Occurrences +-- import Handler.Utils.Occurrences +import Handler.Utils.Avs import qualified Data.Set as Set import qualified Data.Map as Map @@ -29,8 +29,8 @@ import qualified Database.Esqueleto.Experimental as E import qualified Database.Esqueleto.PostgreSQL as E import qualified Database.Esqueleto.Legacy as EL (on, from) -- only `on` and `from` are different, needed for dbTable using Esqueleto.Legacy import qualified Database.Esqueleto.Utils as E -import Database.Esqueleto.PostgreSQL.JSON ((@>.)) -import qualified Database.Esqueleto.PostgreSQL.JSON as E hiding ((?.)) +-- import Database.Esqueleto.PostgreSQL.JSON ((@>.)) +-- import qualified Database.Esqueleto.PostgreSQL.JSON as E hiding ((?.)) import Database.Esqueleto.Utils.TH @@ -528,7 +528,7 @@ mkDailyTable isAdmin ssh nd dcrs = getDayTutorials ssh (nd,nd) >>= \case -- in result , maybeEmpty dcrs $ \DayCheckResults{..} -> sortable (Just "check-fail") (timeCell dcrTimestamp) $ \(view $ resultParticipant . _entityKey -> tpid) -> - maybeCell (Map.lookup tpid dcrResults) $ wgtCell . dcr2widget' Nothing + maybeCell (Map.lookup tpid dcrResults) $ wgtCell . dcr2widgetIcn Nothing , colUserNameModalHdr MsgCourseParticipant ForProfileDataR , colUserMatriclenr isAdmin , sortable (Just "card-no") (i18nCell MsgAvsCardNo) $ \(preview $ resultUserAvs . _userAvsLastCardNo . _Just -> cn :: Maybe AvsFullCardNo) -> cellMaybe (textCell . tshowAvsFullCardNo) cn @@ -682,6 +682,7 @@ postSchoolDayR ssh nd = do setTitleI (MsgMenuSchoolDay ssh dday) $(i18nWidgetFile "day-view") + -- | A wrapper for several check results on tutorial participants data DayCheckResult = DayCheckResult { dcEyeFitsPermit :: Maybe Bool @@ -689,7 +690,7 @@ data DayCheckResult = DayCheckResult , dcApronAccess :: Bool , dcBookingFirmOk :: Bool } - deriving (Show, Generic, Binary) + deriving (Eq, Show, Generic, Binary) data DayCheckResults = DayCheckResults { dcrTimestamp :: UTCTime @@ -697,13 +698,72 @@ data DayCheckResults = DayCheckResults } deriving (Show, Generic, Binary) -type ParticipantCheckData = (Entity TutorialParticipant, UserDisplayName, UserSurname, Maybe AvsPersonId, Maybe CompanyName) +-- | True iff there is no problem at all +dcrIsOk :: DayCheckResult -> Bool +dcrIsOk (DayCheckResult (Just True) True True True) = True +dcrIsOk _ = False +-- | defines categories on DayCheckResult, implying an ordering, with most severe being least +dcrSeverity :: DayCheckResult -> Int +dcrSeverity DayCheckResult{dcAvsKnown = False } = 1 +dcrSeverity DayCheckResult{dcApronAccess = False } = 2 +dcrSeverity DayCheckResult{dcBookingFirmOk = False } = 3 +dcrSeverity DayCheckResult{dcEyeFitsPermit = Nothing } = 4 +dcrSeverity DayCheckResult{dcEyeFitsPermit = Just False} = 5 +dcrSeverity _ = 99 + +instance Ord DayCheckResult where + compare = compare `on` dcrSeverity + +type DayCheckGroups = ( Set TutorialParticipantId -- 1 severity + , Set TutorialParticipantId -- 2 + , Set TutorialParticipantId -- 3 + , Set TutorialParticipantId -- 4 + , Set TutorialParticipantId -- 5 + ) + +dcrSeverityGroups :: Map TutorialParticipantId DayCheckResult -> DayCheckGroups +dcrSeverityGroups = Map.foldMapWithKey groupBySeverity + where + groupBySeverity :: TutorialParticipantId -> DayCheckResult -> DayCheckGroups + groupBySeverity tpid dcr = + let sempty = mempty :: DayCheckGroups + in case dcrSeverity dcr of + 1 -> set _1 (Set.singleton tpid) sempty + 2 -> set _2 (Set.singleton tpid) sempty + 3 -> set _3 (Set.singleton tpid) sempty + 4 -> set _4 (Set.singleton tpid) sempty + 5 -> set _5 (Set.singleton tpid) sempty + _ -> sempty + +-- | Show most important problem as text +dcr2widgetTxt :: Maybe CompanyName -> DayCheckResult -> Widget +dcr2widgetTxt _ DayCheckResult{dcAvsKnown=False} = i18n MsgAvsPersonSearchEmpty +dcr2widgetTxt _ DayCheckResult{dcApronAccess=False} = i18n MsgAvsNoApronCard +dcr2widgetTxt mcn DayCheckResult{dcBookingFirmOk=False} = i18n $ MsgAvsNoCompanyCard mcn +dcr2widgetTxt _ DayCheckResult{dcEyeFitsPermit=Nothing} = i18n MsgCheckEyePermitMissing +dcr2widgetTxt _ DayCheckResult{dcEyeFitsPermit=Just False}= i18n MsgCheckEyePermitIncompatible +dcr2widgetTxt _ _ = i18n MsgNoProblem + +-- | Show all problems as icon with tooltip +dcr2widgetIcn :: Maybe CompanyName -> DayCheckResult -> Widget +dcr2widgetIcn mcn DayCheckResult{..} = mconcat [avsChk, apronChk, bookChk, permitChk] + where + mkTooltip ico msg = iconTooltip msg (Just ico) True + + avsChk = guardMonoid (not dcAvsKnown) $ mkTooltip IconUserUnknown (i18n MsgAvsPersonSearchEmpty) + apronChk = guardMonoid (not dcApronAccess) $ mkTooltip IconUserBadge (i18n MsgAvsNoApronCard) + bookChk = guardMonoid (not dcBookingFirmOk) $ mkTooltip IconCompanyWarning (i18n $ MsgAvsNoCompanyCard mcn) + permitChk | isNothing dcEyeFitsPermit = mkTooltip IconFileMissing (i18n MsgCheckEyePermitMissing) + | dcEyeFitsPermit == Just False = mkTooltip IconGlasses (i18n MsgCheckEyePermitIncompatible) + | otherwise = mempty + +type ParticipantCheckData = (Entity TutorialParticipant, UserDisplayName, UserSurname, Maybe AvsPersonId, Maybe CompanyName) dayCheckParticipant :: Map AvsPersonId AvsDataPerson -> ParticipantCheckData -> DayCheckResult -dayCheckParticipant avsStats (Entity {entityVal=TutorialParticipant{..}}, udn, usn, mapi, mcmp) = +dayCheckParticipant avsStats (Entity {entityVal=TutorialParticipant{..}}, _udn, _usn, mapi, mcmp) = let dcEyeFitsPermit = liftM2 eyeExamFitsDrivingPermit tutorialParticipantEyeExam tutorialParticipantDrivingPermit (dcAvsKnown, (dcApronAccess, dcBookingFirmOk)) | Just AvsDataPerson{avsPersonPersonCards = apcs} <- lookupMaybe avsStats mapi @@ -721,57 +781,6 @@ dayCheckParticipant avsStats (Entity {entityVal=TutorialParticipant{..}}, udn, u fitsBooking (Just cn) AvsDataPersonCard{avsDataValid=True,avsDataFirm=Just df} = Any $ cn == stripCI df fitsBooking _ _ = Any False -dcrIsOk :: DayCheckResult -> Bool -dcrIsOk (DayCheckResult (Just True) True True True) = True -dcrIsOk _ = False - --- TODO: i18n and use icons to show all results at once --- TODO: using memcache, display icons in column in daily view, if cache is filled -dcr2widget :: Maybe CompanyName -> DayCheckResult -> Widget -dcr2widget _ DayCheckResult{dcAvsKnown=False} = text2widget "AVS Abfrage fehlgeschlagen" -dcr2widget _ DayCheckResult{dcApronAccess=False} = text2widget "Kein gültiger Ausweis mit Vorfeld-Zugang gefunden" -dcr2widget mcn DayCheckResult{dcBookingFirmOk=False} = [whamlet|Für buchende Firma #{maybeMonoid mcn} liegt kein gültiger Ausweis vor|] -dcr2widget _ DayCheckResult{dcEyeFitsPermit=Nothing} = text2widget "Sehtest oder Führerschein fehlen noch" -dcr2widget _ DayCheckResult{dcEyeFitsPermit=Just False}= text2widget "Sehtest und Führerschein passen nicht zusammen" -dcr2widget _ _ = text2widget "Kein Problem vorhanden" - - -dcrSeverity :: DayCheckResult -> Int -dcrSeverity DayCheckResult{dcAvsKnown=False} = 1 -dcrSeverity DayCheckResult{dcApronAccess=False} = 2 -dcrSeverity DayCheckResult{dcBookingFirmOk=False} = 3 -dcrSeverity DayCheckResult{dcEyeFitsPermit=Nothing} = 4 -dcrSeverity DayCheckResult{dcEyeFitsPermit=Just False} = 5 -dcrSeverity _ = 99 - -dcrSeverityGroups :: Map TutorialParticipantId DayCheckResult -> (Set TutorialParticipantId,Set TutorialParticipantId,Set TutorialParticipantId,Set TutorialParticipantId,Set TutorialParticipantId) -dcrSeverityGroups dcrs = Map.foldMapWithKey groupBySeverity mempty - where - groupBySeverity :: TutorialParticipantId -> DayCheckResult -> (Set TutorialParticipantId,Set TutorialParticipantId,Set TutorialParticipantId,Set TutorialParticipantId,Set TutorialParticipantId) - groupBySeverity tpid dcr = - let sempty = mempty :: (Set TutorialParticipantId,Set TutorialParticipantId,Set TutorialParticipantId,Set TutorialParticipantId,Set TutorialParticipantId) - in case dcrSeverity dcr of - 1 -> set _1 (Set.singleton tpid) sempty - 2 -> set _2 (Set.singleton tpid) sempty - 3 -> set _3 (Set.singleton tpid) sempty - 4 -> set _4 (Set.singleton tpid) sempty - 5 -> set _5 (Set.singleton tpid) sempty - _ -> sempty - --- Alternative version using icons to display everything at once -dcr2widget' :: Maybe CompanyName -> DayCheckResult -> Widget -dcr2widget' mcn DayCheckResult{..} = mconcat [avsChk, apronChk, bookChk, permitChk] - where - mkTooltip ico msg = iconTooltip msg (Just ico) True - - avsChk = guardMonoid (not dcAvsKnown) $ mkTooltip IconUserUnknown (text2widget "AVS Abfrage fehlgeschlagen") - apronChk = guardMonoid (not dcApronAccess) $ mkTooltip IconUserBadge (text2widget "Kein gültiger Ausweis mit Vorfeld-Zugang gefunden") - bookChk = guardMonoid (not dcBookingFirmOk) $ mkTooltip IconCompanyWarning [whamlet|Für buchende Firma #{maybeMonoid mcn} liegt kein gültiger Ausweis vor|] - permitChk | isNothing dcEyeFitsPermit = mkTooltip IconFileMissing (text2widget "Sehtest oder Führerschein fehlen noch") - | dcEyeFitsPermit == Just False = mkTooltip IconGlasses (text2widget "Sehtest und Führerschein passen nicht zusammen") - | otherwise = mempty - - -- | Prüft die Teilnehmer der Tagesansicht: AVS online aktualisieren, gültigen Vorfeldausweis prüfen, buchende Firma mit Ausweisnummer aus AVS abgleichen getSchoolDayCheckR :: SchoolId -> Day -> Handler Html getSchoolDayCheckR ssh nd = do @@ -813,25 +822,25 @@ getSchoolDayCheckR ssh nd = do udn = pcd ^. _2 ok = maybe False dcrIsOk $ Map.lookup pid participantResults in if ok then acc else Map.insertWith (<>) tid (Map.singleton (udn,pid) pcd) acc - badTutPartMap :: Map TutorialId (Map (UserDisplayName, TutorialParticipantId) ParticipantCheckData) + badTutPartMap :: Map TutorialId (Map (UserDisplayName, TutorialParticipantId) ParticipantCheckData) -- UserDisplayName as Key ensures proper sort order badTutPartMap = foldl' sortBadParticipant mempty parts_avs mkBaddieWgt :: TutorialParticipantId -> ParticipantCheckData -> Widget mkBaddieWgt pid pcd = let name = nameWidget (pcd ^. _2) (pcd ^. _3) bookFirm = pcd ^. _5 - problems = maybe (text2widget "???") (dcr2widget bookFirm) (Map.lookup pid participantResults) - problems' = maybe mempty (dcr2widget' bookFirm) (Map.lookup pid participantResults) -- TODO: decide which version to use - in [whamlet|^{name}: ^{problems'} ^{problems}|] + problemText = maybe (text2widget "???") (dcr2widgetTxt bookFirm) (Map.lookup pid participantResults) + problemIcons = maybe mempty (dcr2widgetIcn bookFirm) (Map.lookup pid participantResults) + in [whamlet|^{name}: ^{problemIcons} ^{problemText}|] siteLayoutMsg MsgMenuSchoolDayCheck $ do - setTitleI MsgMenuSchoolDayCheck -- TODO: i18n + setTitleI MsgMenuSchoolDayCheck [whamlet|

_{MsgMenuSchoolDay ssh dday}

$if Map.null badTutPartMap - Es wurden keine Probleme gefunden. + _{MsgNoProblem}. $else

$forall (tid,badis) <- Map.toList badTutPartMap @@ -839,9 +848,9 @@ getSchoolDayCheckR ssh nd = do #{maybe "???" fst (Map.lookup tid tuts)}
    - $forall ((udn,pid),pcd) <- Map.toList badis + $forall ((_udn,pid),pcd) <- Map.toList badis
  • ^{mkBaddieWgt pid pcd}

    - ^{linkButton mempty (text2widget "Schliessen") [BCIsButton, BCPrimary] (SomeRoute (SchoolR ssh (SchoolDayR nd)))} + ^{linkButton mempty (i18n MsgBtnCloseReload) [BCIsButton, BCPrimary] (SomeRoute (SchoolR ssh (SchoolDayR nd)))} |] \ No newline at end of file diff --git a/src/Utils.hs b/src/Utils.hs index 2b34d3575..67c485fa0 100644 --- a/src/Utils.hs +++ b/src/Utils.hs @@ -420,6 +420,7 @@ int2widget i = [whamlet|#{tshow i}|] word2widget :: Word64 -> WidgetFor site () word2widget i = [whamlet|#{tshow i}|] +-- | for convenience, alternative use Utils.Widgets.i18n directly msg2widget :: RenderMessage site a => a -> WidgetFor site () msg2widget msg = [whamlet|_{msg}|]