{-# LANGUAGE FlexibleContexts,DeriveGeneric #-} module Villefort.Todo (getTodos,getTheme,subject,updateTodos,deleteTodo,qetTasks',Task(..),daysUntil) where import Villefort.Definitions (VConfig(..)) import Villefort.Database (execQuery,getDb) import Villefort.Util (getHeader) import Data.List.Split (splitOn) import Data.ByteString.Lazy (ByteString) import Paths_Villefort (getDataFileName) import Database.HDBC (SqlValue,fromSql,quickQuery',disconnect) import Control.Monad.Reader (MonadReader,MonadIO,liftIO,ask) import Data.Time (getZonedTime,fromGregorian,diffDays) import Data.Aeson import GHC.Generics data Task = Task {rid :: Int, title :: String, description :: String, due :: String, subject :: String, time :: Int, dueIn :: Integer } deriving (Show,Eq,Generic) instance FromJSON Task instance ToJSON Task -- | difference betweeen the current day and the supplied day daysUntil :: [Char] -> IO Integer daysUntil date = do let splits = splitOn "-" date current <- show <$> getZonedTime let dateWhenDue = fromGregorian (read (splits !! 0) :: Integer) (read (splits !! 1) :: Int) (read (splits !! 2) :: Int) let currentDateSplit = splitOn "-" current let currentDate = fromGregorian ( read (currentDateSplit !! 0) :: Integer) (read (currentDateSplit !! 1) :: Int) (read (take 2 ( currentDateSplit !! 2)) :: Int) return $ (diffDays dateWhenDue currentDate) -- | Used to create row takes time spent as an integer --toRow :: [String] -> Int -> Task toRow x = Task (read (x !! 0) :: Int) (x !! 1) (x !! 2) (x !! 3)( x !! 4) -- | update the time taken on a certain project updateTodos :: (MonadReader VConfig m, MonadIO m) => Int -> Int -> m () updateTodos sqlId timeTaken = execQuery "insert into todo (id,Description,Title,Entered,Due,state,time,Subject) select id,Description,Title,Entered,datetime('now', 'localtime'),0,?,Subject from todo where id = ? limit 1" [ timeTaken, sqlId] -- | removes task from database delTask :: (MonadReader VConfig m, MonadIO m) => Int -> m () delTask sqlId = execQuery "update todo set state = 0 where id = ?" [sqlId] -- | returns time spent on task based off of Task id getTime :: (MonadReader VConfig m,MonadIO m) => String -> m Int getTime taskId = do idval <- makeQuery' $ "select sum(time) from todo where id = " ++ show taskId pure $ (read ((idval !! 0) !! 0) :: Int) -- | returns Row data structures for each open task qetTasks' :: (MonadReader VConfig m, MonadIO m) => m [Task] qetTasks' = do x <- makeQuery' "select id, Title, Description, Due, Subject, pred from todo where state=1 group by id order by Due" let ids = map head x times <- mapM getTime ids let dues = map (\x -> x !! 3) x dueIn <- liftIO $ mapM daysUntil dues let halfRows = (map toRow x) :: [Int -> Integer -> Task] return $ apply halfRows times dueIn -- | applies a list of functions to a list of values apply :: [Int -> Integer -> Task] -> [Int] -> [Integer] -> [Task] apply (x:xs) (y:ys) (z:zs) = [x y z] ++ apply xs ys zs apply _ _ _ = [] convRow' :: [[SqlValue]] -> [[String]] convRow' dat = Prelude.map (\x -> Prelude.map (\y -> conv' y ) x) dat -- | Converts from SqlVal to String conv' :: SqlValue -> String conv' x = case fromSql x of Just y -> fromSql y :: String Nothing -> "0" -- | makes Query that returns string makeQuery' :: (MonadReader VConfig m, MonadIO m) => String -> m [[String]] makeQuery' query = do conn <- getDb taskRaw <- liftIO $ quickQuery' conn query [] liftIO $ disconnect conn return (convRow' taskRaw) -- | merges two lists merge :: [a] -> [a] -> [a] merge [] ys = ys merge (x:xs) ys = x:merge ys xs -- | generates modal for task based of Row data Structure genModal' :: Task -> IO String genModal' row = if rid row == 1 then return (" ") else do let f = due row modal <- getModal let da = [daysToColor' $ dueIn row , show $ rid row, (convTitle $ title row) ++ "Due in " ++ ( show $ dueIn row), show $ rid row, title row, description row, show $ time row, "/delete", show $ rid row ] return $ mconcat $ merge modal da -- | Generates bootstrap color from days until due daysToColor' :: (Num a, Ord a) => a -> String daysToColor' x = if x < 1 then "btn-due0" else if x == 1 then "btn-due1" else if x == 2 then "btn-due2" else if x == 3 then "btn-due3" else if x == 4 then "btn-due4" else if x == 5 then "btn-due5" else if x == 6 then "btn-due6" else "btn-due7" -- | reduce size of title to fit in bootstrap modal preview convTitle :: String -> String convTitle longTitle | length s1 > 30 = s1 | length s2 > 30 = s2 | length s3 > 30 = s3 | length s4 > 30 = s4 | otherwise = longTitle where splits = (Data.List.Split.splitOn "." longTitle) s1 = (splits !! 0) s2 = mconcat (take 2 splits) s3 = mconcat (take 3 splits) s4 = mconcat (take 4 splits) -- | returns the modal template for bootstrap getModal :: IO [String] getModal = do path <- getDataFileName "templates/modal.ts" rawModal <- readFile path return (Data.List.Split.splitOn "}" rawModal) -- | Returns html from todos getTodos :: (MonadReader VConfig m, MonadIO m) => m String getTodos = do tasks <- qetTasks' modals <-liftIO $ sequence $ genModal' <$> tasks header <- getHeader theme <- getTheme let body = Prelude.concat modals return (header ++ theme ++ body) -- | support for user defined themes in villefort config getTheme :: (MonadReader VConfig m, MonadIO m) => m String getTheme = do userConfig <- ask let userColor = colors userConfig let mix = zip [0 ..] userColor :: [(Int,String)] return $ "" where genSelector x = ".btn-due" ++ show (fst x) ++ "{ \n background:" ++ (snd x ) ++ "; \n color: #ffffff; }\n" -- | Delete a done task from database sets state = 0 but it's record is still maintained in the database for the stats page. deleteTodo :: (MonadReader VConfig m, MonadIO m) => ByteString -> m () deleteTodo raw = do let da = Data.List.Split.splitOn "&" (show raw) let rawid = Data.List.Split.splitOn "=" $ (Prelude.init (da !! 1)) let sqlId = read (rawid!! 1) :: Int let rawtime = Data.List.Split.splitOn "=" $ (da !! 0) let integerTime = read (rawtime !! 1) :: Int do updateTodos sqlId integerTime -- update task time if integerTime /= 0 then delTask sqlId -- then remove from database else delTask sqlId -- otherwise just remove from database return ()