feat(csv import): add explanation text

This commit is contained in:
Steffen Jost 2019-08-20 15:02:23 +02:00
parent 3c4172cbc2
commit 6d0a4c156b
7 changed files with 78 additions and 47 deletions

View File

@ -181,7 +181,7 @@ CourseApplicationRated: Bewertung erfolgreich angepasst
CourseApplicationRatingDeleted: Bewertung erfolgreich entfernt CourseApplicationRatingDeleted: Bewertung erfolgreich entfernt
CourseApplicationDeleted csh@CourseShorthand: Bewerbung zu #{csh} erfolgreich zurückgezogen CourseApplicationDeleted csh@CourseShorthand: Bewerbung zu #{csh} erfolgreich zurückgezogen
CourseApplicationTitle displayName@Text csh@CourseShorthand: Bewerbung für #{csh}: #{displayName} CourseApplicationTitle displayName@Text csh@CourseShorthand: Bewerbung für #{csh}: #{displayName}
CourseApplicationText: Text-Bewerbung CourseApplicationText: Text-Bewerbung
CourseApplicationFollowInstructions: Beachten Sie die Anweisungen zur Bewerbung! CourseApplicationFollowInstructions: Beachten Sie die Anweisungen zur Bewerbung!
@ -190,7 +190,7 @@ CourseRegistrationFollowInstructions: Beachten Sie die Anweisungen zur Anmeldung
CourseApplicationFile: Bewerbung CourseApplicationFile: Bewerbung
CourseApplicationFiles: Bewerbungsdatei(en) CourseApplicationFiles: Bewerbungsdatei(en)
CourseApplicationArchive: Zip-Archiv der Bewerbungsdatei(en) CourseApplicationArchive: Zip-Archiv der Bewerbungsdatei(en)
CourseRegistrationFile: Datei zur Anmeldung CourseRegistrationFile: Datei zur Anmeldung
CourseRegistrationFiles: Datei(en) zur Anmeldung CourseRegistrationFiles: Datei(en) zur Anmeldung
CourseRegistrationArchive: Zip-Archiv der Datei(en) zur Anmeldung CourseRegistrationArchive: Zip-Archiv der Datei(en) zur Anmeldung
@ -1378,6 +1378,7 @@ CsvImportConfirmationTip: Durch den CSV-Import würden die unten aufgeführten
CsvImportUnnecessary: Durch den CSV-Import würden keine Änderungen vorgenommen werden CsvImportUnnecessary: Durch den CSV-Import würden keine Änderungen vorgenommen werden
CsvImportSuccessful n@Int: CSV-Import erfolgreich, es #{pluralDE n "wurde eine Aktion" (mappend (mappend "wurden " (toMessage n)) " Aktionen")} durchgeführt CsvImportSuccessful n@Int: CSV-Import erfolgreich, es #{pluralDE n "wurde eine Aktion" (mappend (mappend "wurden " (toMessage n)) " Aktionen")} durchgeführt
CsvImportAborted: CSV-Import abgebrochen CsvImportAborted: CSV-Import abgebrochen
CsvImportExplanationLabel: Hinweise zum CSV-Import
Proportion c@Text of@Text prop@Rational: #{c}/#{of} (#{rationalToFixed2 (100 * prop)}%) Proportion c@Text of@Text prop@Rational: #{c}/#{of} (#{rationalToFixed2 (100 * prop)}%)

View File

@ -15,10 +15,6 @@ import Data.CaseInsensitive (original)
-- import qualified Data.CaseInsensitive as CI -- import qualified Data.CaseInsensitive as CI
import qualified Data.Conduit.List as Conduit import qualified Data.Conduit.List as Conduit
import Language.Haskell.TH
import Language.Haskell.TH.Syntax (qRunIO)
-- import Language.Haskell.TH.Datatype
import Text.Hamlet (shamletFile) import Text.Hamlet (shamletFile)
import Handler.Utils.DateTime as Handler.Utils import Handler.Utils.DateTime as Handler.Utils
@ -32,12 +28,9 @@ import Handler.Utils.Rating as Handler.Utils hiding (extractRatings)
import Handler.Utils.Sheet as Handler.Utils import Handler.Utils.Sheet as Handler.Utils
import Handler.Utils.Mail as Handler.Utils import Handler.Utils.Mail as Handler.Utils
import Handler.Utils.ContentDisposition as Handler.Utils import Handler.Utils.ContentDisposition as Handler.Utils
import Handler.Utils.I18n as Handler.Utils
import System.Directory (listDirectory) import System.FilePath.Posix (takeFileName)
import System.FilePath.Posix (takeBaseName, takeFileName)
import qualified Data.List as List
import qualified Data.List.NonEmpty as NonEmpty
import Control.Monad.Logger import Control.Monad.Logger
@ -218,36 +211,6 @@ warnTermDays tid timeNames = do
forM_ outoflecture $ warnI MsgDayIsOutOfLecture forM_ outoflecture $ warnI MsgDayIsOutOfLecture
forM_ outoftermdays $ warnI MsgDayIsOutOfTerm forM_ outoftermdays $ warnI MsgDayIsOutOfTerm
-- | Add language dependent template files
--
-- For large files which are translated as a whole.
--
-- Argument musst be a directory under @/templates@,
-- which contains a file for each language,
-- eg. @imprint@ for choosing between
-- @/templates/imprint/de.hamlet@, @/templates/imprint/de-at.hamlet@,
-- and @/templates/imprint/en.hamlet@
--
-- Dependency detection cannot work properly (no `addDependentFile`-equivalent
-- for directories)
-- @$ stack clean@ is required so new translations show up
i18nWidgetFile :: FilePath -> Q Exp
i18nWidgetFile basename = do
-- Construct list of available translations (@de@, @en@, ...) at compile time
let i18nDirectory = "templates" </> "i18n" </> basename
availableFiles <- qRunIO $ listDirectory i18nDirectory
let availableTranslations = sortWith (NTop . flip List.elemIndex (NonEmpty.toList appLanguages)) . List.nub $ pack . takeBaseName <$> availableFiles
availableTranslations' <- maybe (fail $ "" <> i18nDirectory <> " is empty") return $ NonEmpty.nonEmpty availableTranslations
-- Dispatch to correct language (depending on user settings via `selectLanguage`) at run time
ws <- newName "ws" -- Name for dispatch function
letE
[ funD ws $ [ clause [litP $ stringL l] (normalB . widgetFile $ "i18n" </> basename </> l) []
| l <- unpack <$> NonEmpty.toList availableTranslations' -- One function definition for every available language
] ++ [ clause [wildP] (normalB [e| error "selectLanguage returned an invalid translation" |]) [] ] -- Fallback mostly there so compiler does not complain about non-exhaustive pattern match
] [e|selectLanguage availableTranslations' >>= $(varE ws)|]
-- | return a value only if the current user ist authorized for a given route -- | return a value only if the current user ist authorized for a given route
guardAuthorizedFor :: ( HandlerSite h ~ UniWorX, MonadHandler h, MonadLogger h guardAuthorizedFor :: ( HandlerSite h ~ UniWorX, MonadHandler h, MonadLogger h

43
src/Handler/Utils/I18n.hs Normal file
View File

@ -0,0 +1,43 @@
module Handler.Utils.I18n
where
import Import
import Language.Haskell.TH
import Language.Haskell.TH.Syntax (qRunIO)
import qualified Data.List as List
import qualified Data.List.NonEmpty as NonEmpty
import System.Directory (listDirectory)
import System.FilePath.Posix (takeBaseName)
-- | Add language dependent template files
--
-- For large files which are translated as a whole.
--
-- Argument musst be a directory under @/templates@,
-- which contains a file for each language,
-- eg. @imprint@ for choosing between
-- @/templates/imprint/de.hamlet@, @/templates/imprint/de-at.hamlet@,
-- and @/templates/imprint/en.hamlet@
--
-- Dependency detection cannot work properly (no `addDependentFile`-equivalent
-- for directories)
-- @$ stack clean@ is required so new translations show up
i18nWidgetFile :: FilePath -> Q Exp
i18nWidgetFile basename = do
-- Construct list of available translations (@de@, @en@, ...) at compile time
let i18nDirectory = "templates" </> "i18n" </> basename
availableFiles <- qRunIO $ listDirectory i18nDirectory
let availableTranslations = sortWith (NTop . flip List.elemIndex (NonEmpty.toList appLanguages)) . List.nub $ pack . takeBaseName <$> availableFiles
availableTranslations' <- maybe (fail $ "" <> i18nDirectory <> " is empty") return $ NonEmpty.nonEmpty availableTranslations
-- Dispatch to correct language (depending on user settings via `selectLanguage`) at run time
ws <- newName "ws" -- Name for dispatch function
letE
[ funD ws $ [ clause [litP $ stringL l] (normalB . widgetFile $ "i18n" </> basename </> l) []
| l <- unpack <$> NonEmpty.toList availableTranslations' -- One function definition for every available language
] ++ [ clause [wildP] (normalB [e| error "selectLanguage returned an invalid translation" |]) [] ] -- Fallback mostly there so compiler does not complain about non-exhaustive pattern match
] [e|selectLanguage availableTranslations' >>= $(varE ws)|]

View File

@ -42,6 +42,7 @@ import Handler.Utils.Table.Pagination.CsvColumnExplanations
import Handler.Utils.Form import Handler.Utils.Form
import Handler.Utils.Csv import Handler.Utils.Csv
import Handler.Utils.ContentDisposition import Handler.Utils.ContentDisposition
import Handler.Utils.I18n
import Utils import Utils
import Utils.Lens import Utils.Lens
@ -281,7 +282,7 @@ piIsUnset PaginationInput{..} = and
, isNothing piPage , isNothing piPage
] ]
data DBCsvActionMode = DBCsvActionNew | DBCsvActionExisting | DBCsvActionMissing data DBCsvActionMode = DBCsvActionNew | DBCsvActionExisting | DBCsvActionMissing
deriving (Read, Show, Eq, Ord, Enum, Bounded, Generic, Typeable) deriving (Read, Show, Eq, Ord, Enum, Bounded, Generic, Typeable)
instance Universe DBCsvActionMode instance Universe DBCsvActionMode
@ -851,6 +852,7 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
, formSubmit = FormSubmit , formSubmit = FormSubmit
, formAnchor = Nothing :: Maybe Text , formAnchor = Nothing :: Maybe Text
} }
csvImportExplanation = modal [whamlet|_{MsgCsvImportExplanationLabel}|] $ Right $(i18nWidgetFile "table/csv-import-explanation")
csvColExplanations = case dbtCsvEncode of csvColExplanations = case dbtCsvEncode of
(Just (Dict, _) :: DBTCsvEncode _ csv) -> assertM' (not . null) . Map.toList . csvColumnsExplanations $ Proxy @csv (Just (Dict, _) :: DBTCsvEncode _ csv) -> assertM' (not . null) . Map.toList . csvColumnsExplanations $ Proxy @csv
Nothing -> Nothing Nothing -> Nothing
@ -921,7 +923,7 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
| otherwise | otherwise
-> return $ DBCsvDiffNew rowKey row -> return $ DBCsvDiffNew rowKey row
mapM_ fileSourceCsv dbCsvFiles .| C.mapM toDiff mapM_ fileSourceCsv dbCsvFiles .| C.mapM toDiff
seen <- State.get seen <- State.get
forM_ (Map.toList existing) $ \(rowKey, oldRow) -> if forM_ (Map.toList existing) $ \(rowKey, oldRow) -> if
| Map.member rowKey seen -> return () | Map.member rowKey seen -> return ()
@ -938,7 +940,7 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
-> let doHandle -> let doHandle
| Just inpCsv <- x ^? _dbCsvNew | Just inpCsv <- x ^? _dbCsvNew
= handle $ throwM . (DBCsvException (toNamedRecord inpCsv) :: Text -> DBCsvException k') <=< dbtCsvRenderException = handle $ throwM . (DBCsvException (toNamedRecord inpCsv) :: Text -> DBCsvException k') <=< dbtCsvRenderException
| otherwise | otherwise
= id = id
in C.sourceList <=< lift . doHandle . runConduit $ dbtCsvComputeActions x .| C.foldMap pure in C.sourceList <=< lift . doHandle . runConduit $ dbtCsvComputeActions x .| C.foldMap pure
innerAct .| C.fold accActionMap Map.empty innerAct .| C.fold accActionMap Map.empty
@ -954,7 +956,7 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
let let
precomputeIdents :: forall f m'. (Eq (Element f), MonoFoldable f, MonadHandler m') => f -> m' (Element f -> Text) precomputeIdents :: forall f m'. (Eq (Element f), MonoFoldable f, MonadHandler m') => f -> m' (Element f -> Text)
precomputeIdents = foldM (\f act -> (\id' x -> bool (f x) id' $ act == x) <$> newIdent) (\_ -> error "No id precomputed") precomputeIdents = foldM (\f act -> (\id' x -> bool (f x) id' $ act == x) <$> newIdent) (\_ -> error "No id precomputed")
actionClassIdent <- precomputeIdents $ Map.keys actionMap actionClassIdent <- precomputeIdents $ Map.keys actionMap
actionIdent <- precomputeIdents . Set.unions $ Map.elems actionMap actionIdent <- precomputeIdents . Set.unions $ Map.elems actionMap
@ -980,7 +982,7 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
, formSubmit = FormSubmit , formSubmit = FormSubmit
, formAnchor = Nothing :: Maybe Text , formAnchor = Nothing :: Maybe Text
} }
$(widgetFile "csv-import-confirmation-wrapper") $(widgetFile "csv-import-confirmation-wrapper")
let defaultHeaderOrder = headerOrder (error "not to be forced" :: csv) let defaultHeaderOrder = headerOrder (error "not to be forced" :: csv)

View File

@ -0,0 +1,21 @@
<h3>Hinweise zum Import von CSV-Dateien
<dl .deflist>
<dt .deflist__dt>Änderungen
<dd .deflist__dd>
Alle Werte können durch den Import verändert werden,
auch bereits Beim Export bereits vorhandene Werte.
<dt .deflist__dt>Vorschau
<dd .deflist__dd>
Bevor Werte geändert werden, wird eine Vorschau der Änderungen angezeigt.
<dt .deflist__dt>Leere Zellen
<dd .deflist__dd>
Löschbare Zellen werden durch leere Zellen gelöscht oder auf eindeutige Werte gesetzt.
<dt .deflist__dt>Konsistenz
<dd .deflist__dd>
<p>
Es werden nur konsistente Änderungen akzeptiert!
<p>
Daraus folgt, dass es sinnvoll sein kann, gewisse Zellen frei zu lassen;
z.B. ändert man ein Studienfachzuordnung eines Teilnehmers ab,
dann müsste man auch Abschluss und Semesterzahl passend ändern.
Da diese jedoch eindeutig sind, kann man diese Zellen einfach frei lassen.

View File

@ -4,6 +4,7 @@ $if is _Just dbtCsvDecode
<h3 .csv-import__toggle uw-show-hide data-show-hide-id=table-csv-import data-show-hide-collapsed> <h3 .csv-import__toggle uw-show-hide data-show-hide-id=table-csv-import data-show-hide-collapsed>
_{MsgTableHeadingCsvImport} _{MsgTableHeadingCsvImport}
<div .csv-import__content> <div .csv-import__content>
^{csvImportExplanation}
^{csvImportWdgt'} ^{csvImportWdgt'}
$if is _Just dbtCsvEncode $if is _Just dbtCsvEncode
<div .csv-export> <div .csv-export>

View File

@ -1,6 +1,6 @@
.csv-export { .csv-export {
margin-bottom: 13px; margin-bottom: 13px;
.csv-export__content { .csv-export__content {
display: flex; display: flex;
align-content: space-between; align-content: space-between;