packages feed

Villefort 0.1.0.7 → 0.1.0.8

raw patch · 12 files changed

+254/−189 lines, 12 filesdep +randomPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: random

API changes (from Hackage documentation)

- Database: conv :: [[SqlValue]] -> [[String]]
- Lib: someFunc :: IO ()
- Stats: Query :: String -> Query
- Stats: data Query
- Stats: getAvg :: IO [[String]]
- Stats: getSum :: IO [[String]]
- Stats: hea :: String
- Stats: makeGithub :: String -> IO String
- Stats: makeRow :: [[Char]] -> [Char]
- Stats: makeTable :: [[[Char]]] -> [Char]
- Todo: Row :: Int -> String -> String -> String -> Int -> Row
- Todo: [description] :: Row -> String
- Todo: [due] :: Row -> String
- Todo: [rid] :: Row -> Int
- Todo: [time] :: Row -> Int
- Todo: [title] :: Row -> String
- Todo: addTime :: Int -> Int -> IO ()
- Todo: convTitle :: [Char] -> [Char]
- Todo: data Row
- Todo: daysTilDue :: [Char] -> IO Integer
- Todo: daysToColor' :: (Num a, Ord a) => a -> [Char]
- Todo: delTask :: Int -> IO ()
- Todo: fromZonedTimeToDay :: [Char] -> Day
- Todo: getModal :: IO [[Char]]
- Todo: merge :: [a] -> [a] -> [a]
- Todo: toRow :: [String] -> Row
+ Database: convRow :: [[SqlValue]] -> [[String]]
+ Todo: conv' :: SqlValue -> String
+ Todo: convRow' :: [[SqlValue]] -> [[String]]
+ Todo: makeQuery' :: String -> IO [[String]]
- Database: getSubjects :: IO [Subject]
+ Database: getSubjects :: IO [String]
- Database: makeQuery :: Query -> IO [[String]]
+ Database: makeQuery :: String -> IO [[String]]

Files

README.md view
@@ -1,10 +1,11 @@ # Villefort
 Villefort is a time managment system written in Haskell.
 
-## What's new in version Villefort-0.1.0.7
-- Sqlite database now saves events using localtime.
-- Days until due is now calculated using localtime.
-- Tracks when you update working time on todo instead of just when you finish your task.
+## What's new in version Villefort-0.1.0.8
+- Pretty colors on the stats page.
+- Fixed time tracking bug.
+- Better UX on the home page.
+
 # Home screen
 ![alt text](https://raw.githubusercontent.com/Chrisr850/Villefort/master/data/screen.png)
 # Add new todos
Villefort.cabal view
@@ -1,5 +1,5 @@ name:                Villefort-version:             0.1.0.7+version:             0.1.0.8 synopsis: Villefort is a task manager and time tracker written in haskell. description: Villefort is a browser based time tracker. homepage:            https://github.com/Chrisr850/Villefort#readme@@ -15,13 +15,13 @@ data-files:            data/todo.db                      , templates/*.html                      , templates/*.ts+                     , templates/header                      , js.js                         library   hs-source-dirs:      src   other-modules:       Paths_Villefort-  exposed-modules:     Lib-                     , Database+  exposed-modules:     Database                      , Todo                      , Stats   build-depends:       base >= 4.9 && < 5@@ -31,6 +31,7 @@                      , time >= 1.6.0 && < 1.7                      , FindBin >= 0.0.5 && < 0.1                      , mtl  >= 2.2.1 && < 2.3+                     , random                          default-language:    Haskell2010
app/Main.hs view
@@ -6,9 +6,9 @@ import Control.Monad.IO.Class import Database.HDBC.Sqlite3  import Database.HDBC-import Data.List.Split+import Data.List.Split  import Data.Text.Lazy.Encoding (decodeUtf8)-import Data.Text.Lazy hiding (splitOn,map,concat)+import Data.Text.Lazy hiding (splitOn,map,concat,head) import Data.Time import Database import Todo@@ -40,12 +40,14 @@    makeNewPage = do-  path <- getDataFileName "templates/add.html"-  rawhtml <- liftIO $ readFile path-  let split = splitOn "?" rawhtml+  headerPath <- getDataFileName "templates/header"+  header <- liftIO $ readFile headerPath+  addPath <- getDataFileName "templates/add.html"+  add <- liftIO $ readFile addPath+  let split = splitOn "?" add   subjects <- getSubjects-  let radiobuttons = map makeRadio subjects-  return ((split !! 0) ++ (concat radiobuttons) ++ (split !! 1))+  let radiobuttons =  map makeRadio subjects+  return (header ++ (split !! 0) ++ (concat radiobuttons) ++ (split !! 1))   -- | Main function of loop main :: IO ()@@ -86,8 +88,11 @@       let date    = convDate $ getIndex parse 3       let title   = rep $ getIndex parse 1       let subject = rep $ getIndex parse 2-      stmt <- liftIO $ prepare conn "insert into todo (Description,Title,Entered,Due,State,time,Subject) Values (?,?,current_date,?,1,0,?)"-      do liftIO $ execute stmt [toSql title,toSql summary,toSql date,toSql subject]+      f <-liftIO $ makeQuery "select id from todo order by id desc"+      let rawid = head $ f+      let id = (read (rawid !! 0) :: Integer) +1+      stmt <- liftIO $ prepare conn "insert into todo (id,Description,Title,Entered,Due,State,time,Subject) Values (?,?,?,current_date,?,1,0,?)"+      do liftIO $ execute stmt [toSql id,toSql title,toSql summary,toSql date,toSql subject]       do liftIO $ commit conn       do liftIO $ disconnect conn       redirect "/"
data/todo.db view

binary file changed (12288 → 16384 bytes)

src/Database.hs view
@@ -14,7 +14,7 @@ type Subject = String  -getSubjects :: IO [Subject]+--getSubjects :: IO (Maybe [Subject]) getSubjects = (\x-> (!! 0) <$> x) <$> makeQuery "select Subject from todo where state = 0 group by Subject"  --path =fmap (\x -> (x !! 0) ++ "Villefort.app/Contents/Resources/") $  (Data.List.Split.splitOn "Villefort.app") <$> getProgPath@@ -25,15 +25,23 @@ path' = getDataDir  getDb :: IO Connection  -getDb =  (++ "data/todo.db") <$> path >>= \path ->   putStrLn path >> connectSqlite3 path+getDb =  (++ "/data/todo.db") <$> path' >>= \path -> putStrLn path >> connectSqlite3 path -conv :: [[SqlValue]] -> [[String]]-conv dat = Prelude.map (\x -> Prelude.map (\y -> fromSql y :: String) x) dat+--convRow :: [[SqlValue]] -> [[String]]+convRow dat = Prelude.map (\x -> Prelude.map (\y -> fromSql y :: String ) x) dat -makeQuery :: Query -> IO [[String]]++--makeQuery :: Query -> IO [[String]]+makeQuery query = do+  conn <- getDb+  taskRaw <- quickQuery' conn query []+  disconnect conn+  return (convRow taskRaw)+  {- makeQuery query =  getDb >>=  \conn  ->  quickQuery' conn query [] >>=-  \taskRaw -> disconnect conn >> return ( conv taskRaw)+  \taskRaw -> disconnect conn >> return ( convRow taskRaw) +-}     execQuery :: Query -> [Int] -> IO ()
− src/Lib.hs
@@ -1,6 +0,0 @@-module Lib-    ( someFunc-    ) where--someFunc :: IO ()-someFunc = putStrLn "someFunc"
src/Stats.hs view
@@ -1,28 +1,69 @@-module Stats where+module Stats (genStats+             )where import Database import Control.Monad.IO.Class+import Paths_Villefort+import System.Random  data Query = Query String +-- | Query to get average of subjects+--getAvg :: IO (Maybe [String]) getAvg =  makeQuery "select avg(time), Subject from todo  group by Subject order by avg(time) desc" +-- | Query to get sum of subjects+--getSum :: IO (Maybe [String]) getSum =  makeQuery "select sum(time), Subject from todo group by Subject order by sum(time) desc" +-- | Helper function to generate row of table makeRow x =  "<tr> <th>" ++ (x !! 0) ++ "</th> <th>  " ++ (x!! 1) ++ "</th> </tr>" +-- | Generate Table makeTable stats = "<table style='width:100%'>" ++ (mconcat (map  makeRow stats)) ++ "</table>" +-- | Generate stats -genStats = getSubjects >>= mapM makeGithub >>= \gits -> getAvg >>=-  \avg -> getSum >>= \sum -> return (hea ++ (makeTable avg) ++ "</br> <h1> Sum </h1>" ++ (makeTable sum) ++ "</html>" ++ (mconcat gits)) -hea :: String-hea = "<!DOCTYPE html> <html> <script src='/js-chart-widgets.min.js'></script><h1> avg </h1>"+genStats = getSubjects >>= \x ->  mapM makeGithub x >>= \gits -> getAvg >>=+  \avg -> getSum >>= \sum -> getDataFileName "templates/header" >>= \x -> liftIO (readFile x) >>= \head -> return (head ++ table ++ (makeTable avg) ++ "</br> <h1> Sum </h1>" ++ (makeTable sum) ++ "</html>" ++ (mconcat gits))+-- | Library to include for github table+table :: String+table = " <script src='/js-chart-widgets.min.js'></script><h1> avg </h1>" -makeGithub :: String -> IO String+-- | creates the github like graph from database+++--makeGithub :: Maybe String -> IO String makeGithub subject = do   z <-   makeQuery ("select Due from todo where subject = '" ++ subject ++ "' and state = 0")+  color <- getColor   let head = "<h2 id='"++ subject ++ "'> Calendar "++ subject ++ " </h2> <script> new Calendar({ append_to: '" ++ subject ++ "',num_weeks: 51,day_size: 11, data: ["   let q = Prelude.map (\x -> "['" ++ (x !! 0) ++"',500],") z-  let bot = "  ], color: '#005500' }); </script>"+  let bot = "  ], color: " ++ color ++ " }); </script>"   return (head ++ (Prelude.concat q )++  bot)++colors = ["'#F44336'"+         ,"'#E91E63'"+         ,"'#9C27B0'"+         ,"'#673AB7'"+         ,"'#3F51B5'"+         ,"'#2196F3'"+         ,"'#03A9F4'"+         ,"'#00BCD4'"+         ,"'#009688'"+         ,"'#4CAF50'"+         ,"'#8BC34A'"+         ,"'#CDDC39'"+         ,"'#FFEB3B'"+         ,"'#FFC107'"+         ,"'#FF9800'"+         ,"'#FF5722'"+         ,"'#795548'"+         ,"'#9E9E9E'"+         ,"'#607D8B'"+         ]++getColor = do+  number <- randomRIO (0, (length $ colors)-1) :: IO Int+  return (colors !! number)+  
src/Todo.hs view
@@ -1,4 +1,12 @@-module Todo where+module Todo (getTodos+            ,updateTask+            ,deleteTodo+            ,qetTasks'+            ,makeQuery'+            ,conv'+            ,convRow'+            ,genModal'+            ) where  import Database import Data.List.Split@@ -6,6 +14,8 @@ import Data.Time import Data.List.Split as S import Paths_Villefort+import Database.HDBC.Sqlite3 +import Database.HDBC   data Row = Row { rid :: Int,@@ -15,22 +25,36 @@                  time :: Int                } deriving (Show) -toRow :: [String] -> Row-toRow x = Row (read (x !! 0) :: Int) (x !! 1) (x !! 2) (x !! 3) (read( x !! 4) :: Int)+--toRow :: Maybe [String] -> Maybe Row+toRow x =  Row (read (x !! 0) :: Int) (x !! 1) (x !! 2) (x !! 3) (read( x !! 4) :: Int) -qetTasks' =  makeQuery " select id, Title, Description, Due, sum(time) from todo where state = 1 group by Title, Description order by Due " >>=-  \x -> return (map toRow x)+qetTasks' = do+  -- I would like to take this moment to personally apoligize to the sql gods for creating this monstrosity.+  x <- makeQuery' "select id, Title, Description, Due,  sum(time) from todo where state=1 group by id "+  return (map toRow x) +convRow' dat = Prelude.map (\x -> Prelude.map (\y -> conv' y ) x) dat +--conv :: SqlValue -> String+conv' x = case fromSql x of+  Just x -> fromSql x :: String+  Nothing -> "1"  +--makeQuery :: Query -> IO [[String]]+makeQuery' query = do+  conn <- getDb+  taskRaw <- quickQuery' conn query []+  disconnect conn+  return (convRow' taskRaw) + merge [] ys = ys merge (x:xs) ys = x:merge ys xs     -genModal' row = do+genModal' row = if rid row == 1 then return (" ") else do   let f = due row   modal <- getModal   days  <- daysTilDue f@@ -40,11 +64,11 @@             show $ rid row,             title row,             description row,-            "/delete",             show $ time row,+            "/delete",             show $ rid row            ]-           +   return $ mconcat $  merge modal da   @@ -90,16 +114,18 @@ fromZonedTimeToDay x = fromGregorian (read (nums !! 0) :: Integer) (read (nums !! 1) :: Int) (read (nums !! 2) :: Int)    where nums = S.splitOn "-" $  take 10 x   -+-- | Returns html from todos  getTodos = do   tasks <-  qetTasks'+  do liftIO $ putStrLn $ show tasks   path  <- path -  modals <- sequence $ Prelude.map genModal'  tasks-  path   <- getDataFileName "templates/index.html"-  header <- readFile path+  modals <- sequence $  genModal' <$>  tasks+  headerPath <- getDataFileName "templates/header"+  header <- liftIO $ readFile headerPath   let body = Prelude.concat modals   return (header ++  body) +-- | Delete a done task from database sets state = 0 but it's record is still maintained in the database for the stats page. deleteTodo raw = do     let da   = Data.List.Split.splitOn "&" (show raw)     let rawid = Data.List.Split.splitOn "=" $  (Prelude.init (da !! 1))@@ -118,6 +144,6 @@ delTask id = execQuery "update todo  set state = 0 where id = ?" [id]  updateTask :: Int -> Int ->  IO ()-updateTask id time = execQuery "insert into todo (Description,Title,Entered,Due,state,time,Subject) select Description,Title,Entered,Due,0,time+?,Subject from todo where id = ?" [ time, id]+updateTask id time = execQuery "insert into todo (id,Description,Title,Entered,Due,state,time,Subject) select id,Description,Title,Entered,Due,1,?,Subject from todo where id = ? limit 1" [ time, id]  
templates/add.html view
@@ -1,40 +1,11 @@-<!doctype html>-<title>Villefort</title>-  <meta charset="utf-8">-	   <meta name="viewport" content="width=device-width, initial-scale=1">-<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='static1.css') }}">-<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">  + <link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">-  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>-  <script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>   <script   src="https://code.jquery.com/jquery-1.10.2.min.js"   integrity="sha256-C6CB9UYIS9UJeqinPHWTHVqh/E1uhG5Twh+Y5qFQmYg="     crossorigin="anonymous"></script>-  <style>-     .page {-    padding-left:20%;-     }-  </style> -<div class="page">-  <h1>Villefort</h1>- -   -    <div align="left">-      <div class="btn-group">-	<div class="dropdown pull-left ">-       <button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">Todo -	 <span id="i1" class="caret"></span></button>-       <ul class="dropdown-menu">-	 <li><a href="/">Home</a></li>-	 <li><a href="/new">New</a></li>-	 <li><a href="/stat"> Log </a> </li>-       </ul>-     </div>-    </div>-    </div>   <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>    <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.css">
+ templates/header view
@@ -0,0 +1,125 @@+<!doctype html>+<title>Villefort</title>+  <meta charset="utf-8">+	+  <meta name="viewport" content="width=device-width, initial-scale=1">+<link rel="shortcut icon" href="/static/favicon.ico">  ++  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>+  <script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>+  <style>+   +   +    /*+    .btn-due7{+    background: #750787;+    color: #ffffff;+    }+    .btn-due6{+    background: #750787;+    color: #ffffff;+    }+    .btn-due5{+background: #008026;+    color: #ffffff;+    }+    .btn-due4{+background: #004DFF;+    color: #ffffff;+    }+    .btn-due3{+background: #008026;+    color: #ffffff;+    }+    .btn-due2{+background: #FFED00;+    color: #ffffff;+    }+    .btn-due1{+background: #FF8C00;+    color: #ffffff;+    }+    .btn-due0{+    background: #E40303;+    color: #ffffff;+    }+    */+  .btn-due11{ background: #3F51B5; color: #ffffff;}.btn-due10{ background: #E8EAF6; color: #ffffff;}.btn-due9{ background: #C5CAE9; color: #ffffff;}.btn-due8{ background: #9FA8DA; color: #ffffff;}.btn-due7{ background: #7986CB; color: #ffffff;}.btn-due6{ background: #5C6BC0; color: #ffffff;}.btn-due5{ background: #3F51B5; color: #ffffff;}.btn-due4{ background: #3949AB; color: #ffffff;}.btn-due3{ background: #303F9F; color: #ffffff;}.btn-due2{ background: #283593; color: #ffffff;}.btn-due1{ background: #1A237E; color: #ffffff;}+    /*+    .btn-due7{+    background: #311B92;+    color: #ffffff;+    }+    .btn-due6{+    background: #750787;+    color: #ffffff;+    }+    .btn-due5{+background: #008026;+    color: #ffffff;+    }+    .btn-due4{+background: #004DFF;+    color: #ffffff;+    }+    .btn-due3{ +background: #008026;+    color: #ffffff;+    }+    .btn-due2{+background: #FFED00;+    color: #ffffff;+    }+    .btn-due1{+background: #FF8C00;+    color: #ffffff;+    }+    .btn-due0{+    background: #311B92;+    color: #ffffff;+    }+    */+    </style>++<div class="page">+  <h1>Villefort</h1>+ +     +  <div class ="todo">+    <div align="left">+      <div class="btn-group">+	<div class="dropdown pull-left ">+       <button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">Todo +	 <span id="i1" class="caret"></span></button>+       <ul class="dropdown-menu">+	 <li><a href="/">Home</a></li>+	 <li><a href="/new">New</a></li>+	 <li><a href="/stat"> Log </a> </li>+       </ul>+	</div>+		+    </div>+    </div>++    </br>+   +    </div>  +<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">+  <script+  src="https://code.jquery.com/jquery-1.10.2.min.js"+  integrity="sha256-C6CB9UYIS9UJeqinPHWTHVqh/E1uhG5Twh+Y5qFQmYg="+    crossorigin="anonymous"></script>+  <style>+     .page {+    padding-left:20%;+     }+  </style> +<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>+<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>+   <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.css">+    <script>+  $(document).ready(function() {+    $("#due").datepicker();+  });+  </script>+  
templates/index.html view
@@ -1,109 +0,0 @@-<!doctype html>-<title>Villefort</title>-  <meta charset="utf-8">-	-  <meta name="viewport" content="width=device-width, initial-scale=1">-<link rel="shortcut icon" href="/static/favicon.ico">  -<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">-  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>-  <script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>-  <style>-    .todo {-    padding-left:20%;-    }-    h1 {-    padding-left:20%;-    }-    /*-    .btn-due7{-    background: #750787;-    color: #ffffff;-    }-    .btn-due6{-    background: #750787;-    color: #ffffff;-    }-    .btn-due5{-background: #008026;-    color: #ffffff;-    }-    .btn-due4{-background: #004DFF;-    color: #ffffff;-    }-    .btn-due3{-background: #008026;-    color: #ffffff;-    }-    .btn-due2{-background: #FFED00;-    color: #ffffff;-    }-    .btn-due1{-background: #FF8C00;-    color: #ffffff;-    }-    .btn-due0{-    background: #E40303;-    color: #ffffff;-    }-    */-  .btn-due11{ background: #3F51B5; color: #ffffff;}.btn-due10{ background: #E8EAF6; color: #ffffff;}.btn-due9{ background: #C5CAE9; color: #ffffff;}.btn-due8{ background: #9FA8DA; color: #ffffff;}.btn-due7{ background: #7986CB; color: #ffffff;}.btn-due6{ background: #5C6BC0; color: #ffffff;}.btn-due5{ background: #3F51B5; color: #ffffff;}.btn-due4{ background: #3949AB; color: #ffffff;}.btn-due3{ background: #303F9F; color: #ffffff;}.btn-due2{ background: #283593; color: #ffffff;}.btn-due1{ background: #1A237E; color: #ffffff;}-    /*-    .btn-due7{-    background: #311B92;-    color: #ffffff;-    }-    .btn-due6{-    background: #750787;-    color: #ffffff;-    }-    .btn-due5{-background: #008026;-    color: #ffffff;-    }-    .btn-due4{-background: #004DFF;-    color: #ffffff;-    }-    .btn-due3{ -background: #008026;-    color: #ffffff;-    }-    .btn-due2{-background: #FFED00;-    color: #ffffff;-    }-    .btn-due1{-background: #FF8C00;-    color: #ffffff;-    }-    .btn-due0{-    background: #311B92;-    color: #ffffff;-    }-    */-    </style>--<div class="page">-  <h1>Villefort</h1>- -     -  <div class ="todo">-    <div align="left">-      <div class="btn-group">-	<div class="dropdown pull-left ">-       <button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">Todo -	 <span id="i1" class="caret"></span></button>-       <ul class="dropdown-menu">-	 <li><a href="/">Home</a></li>-	 <li><a href="/new">New</a></li>-	 <li><a href="/stat"> Log </a> </li>-       </ul>-	</div>-		-    </div>-    </div>--    </br>-   
templates/modal.ts view
@@ -13,14 +13,16 @@         </div>         <div class="modal-body">           <p> } </p>+	  +	  <p> Total minutes spent working on Project } </p>         </div>         <div class="modal-footer"> 	<form class="pull-left" action="}" method="post" class="add-entry">       <dl>         <dd><button type="submit" class="btn btn-success pull-left" >Finished</button>-       &nbsp;	Time <input type="text" name="time" value="}">  <br>+       &nbsp;	Time <input type="text" name="time" value="0">           <input type="hidden" class="pull-left" name="id" value="}">-	  <button type="submit" formaction="/update">update</button>+	  <button type="submit" class="btn btn-warning pull-right" style="margin-left: 20px;" formaction="/update">update</button>       </dl> 	</form>