Add option to show/hide completed items

This commit is contained in:
Cameron Reed 2024-09-05 18:21:52 -06:00
parent d14d1ec468
commit e96bbdb0dd
7 changed files with 35 additions and 19 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
test.db
todo-web

View File

@ -102,7 +102,7 @@ func GetAllTodos() ([]types.Todo, error) {
func GetOverdueTodos() ([]types.Todo, error) {
var todos []types.Todo
rows, err := db.Query("SELECT * FROM items WHERE due < ? AND due IS NOT NULL ORDER BY due", time.Now().Unix())
rows, err := db.Query("SELECT * FROM items WHERE due < ? AND due IS NOT NULL ORDER BY completed, due", time.Now().Unix())
if err != nil {
return nil, err
}
@ -136,7 +136,7 @@ func GetTodayTodos() ([]types.Todo, error) {
now := time.Now()
year, month, day := now.Date()
today := time.Date(year, month, day, 0, 0, 0, 0, time.Local)
rows, err := db.Query("SELECT * FROM items WHERE (start <= ? OR start IS NULL) AND (due >= ? OR due IS NULL) ORDER BY due NULLS LAST", today.Unix(), now.Unix())
rows, err := db.Query("SELECT * FROM items WHERE (start <= ? OR start IS NULL) AND (due >= ? OR due IS NULL) ORDER BY completed, due NULLS LAST", today.Unix(), now.Unix())
if err != nil {
return nil, err
}
@ -169,7 +169,7 @@ func GetUpcomingTodos() ([]types.Todo, error) {
year, month, day := time.Now().Date()
today := time.Date(year, month, day, 0, 0, 0, 0, time.Local)
rows, err := db.Query("SELECT * FROM items WHERE start > ? ORDER BY start", today.Unix())
rows, err := db.Query("SELECT * FROM items WHERE start > ? ORDER BY completed, start", today.Unix())
if err != nil {
return nil, err
}

View File

@ -15,6 +15,7 @@ func main() {
db_path := flag.String("db", "./test.db", "Path to the sqlite3 database")
bind_port := flag.Int("p", 8080, "Port to bind to")
bind_addr := flag.String("a", "0.0.0.0", "Address to bind to")
static_dir := flag.String("static", "./static", "Path to static files")
noFront := flag.Bool("no-frontend", false, "Disable the frontend endpoints")
a := false; noBack := &a // flag.Bool("no-backend", false, "Disable the backend endpoints")
@ -28,7 +29,7 @@ func main() {
}
if !*noFront {
addFrontendEndpoints(mux)
addFrontendEndpoints(mux, *static_dir)
}
if !*noBack {
@ -50,7 +51,7 @@ func main() {
}
}
func addFrontendEndpoints(mux *http.ServeMux) {
func addFrontendEndpoints(mux *http.ServeMux, static_path string) {
fmt.Println("Frontend enabled")
mux.HandleFunc("/", Error404)
@ -64,7 +65,7 @@ func addFrontendEndpoints(mux *http.ServeMux) {
mux.HandleFunc("PUT /update", pages.UpdateItem)
mux.HandleFunc("POST /new", pages.CreateItem)
fileServer := http.FileServer(http.Dir("./static"))
fileServer := http.FileServer(http.Dir(static_path))
mux.Handle("/css/", fileServer)
mux.Handle("/js/", fileServer)
}

View File

@ -8,7 +8,7 @@ import (
templ RootPage() {
<!Doctype HTML>
<html lang="en-US">
<html lang="en-US" data-show-completed="false">
<head>
<title>Todo</title>
@ -30,7 +30,10 @@ templ RootPage() {
<a id="new-button" href="#create-item">New</a>
</div>
<div id="nav-center"></div>
<div id="nav-right"></div>
<div id="nav-right">
<label for="show-completed">Show completed</label>
<input id="show-completed" type="checkbox" name="show-completed"/>
</div>
</div>
</nav>

View File

@ -32,7 +32,7 @@ func RootPage() templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype HTML><html lang=\"en-US\"><head><title>Todo</title><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><link rel=\"stylesheet\" href=\"/css/styles.css\"><script src=\"/js/script.js\"></script><script src=\"/js/lib/htmx.min.js\"></script><!-- Font Awesome --><script src=\"https://kit.fontawesome.com/469cdddb31.js\" crossorigin=\"anonymous\"></script></head><body><nav><div id=\"nav-container\"><div id=\"nav-left\"><a id=\"new-button\" href=\"#create-item\">New</a></div><div id=\"nav-center\"></div><div id=\"nav-right\"></div></div></nav><div id=\"main-content\"><div id=\"lists\"><div id=\"overdue-list\" class=\"todo-list\"><div class=\"todo-list-title\">Overdue</div><div class=\"todo-list-items\" hx-get=\"/overdue\" hx-trigger=\"load\" hx-swap=\"outerHTML\"></div><div class=\"new-item\"></div></div><div id=\"today-list\" class=\"todo-list\"><div class=\"todo-list-title\">Today</div><div class=\"todo-list-items\" hx-get=\"/today\" hx-trigger=\"load\" hx-swap=\"outerHTML\"></div><div class=\"new-item\"></div></div><div id=\"upcoming-list\" class=\"todo-list\"><div class=\"todo-list-title\">Upcoming</div><div class=\"todo-list-items\" hx-get=\"/upcoming\" hx-trigger=\"load\" hx-swap=\"outerHTML\"></div><div class=\"new-item\"></div></div></div><form id=\"create-item\" hx-post=\"/new\" hx-swap=\"none\"><div class=\"form-title\">Create new Todo</div><div class=\"form-container\"><div class=\"form-column\"><label for=\"name\">Name</label><br><input id=\"create-item-name\" type=\"text\" name=\"name\"><br><label for=\"start\">Start</label><br><input id=\"create-item-start\" name=\"start\" type=\"datetime-local\"><br><label for=\"due\">Due</label><br><input id=\"create-item-due\" name=\"due\" type=\"datetime-local\"></div><div class=\"form-column\"></div></div><div class=\"form-button-container\"><button id=\"create-save\" class=\"form-save-button button\" type=\"submit\">Save</button> <a class=\"form-close-button button\" href=\"#\">Close</a></div></form><form id=\"edit-item\" data-id=\"\" hx-put=\"/update\" hx-swap=\"outerHTML\"><div class=\"form-title\">Edit Todo</div><div class=\"form-container\"><div class=\"form-column\"><label for=\"name\">Name</label><br><input id=\"edit-item-name\" type=\"text\" name=\"name\"><br><label for=\"start\">Start</label><br><input id=\"edit-item-start\" name=\"start\" type=\"datetime-local\"><br><label for=\"due\">Due</label><br><input id=\"edit-item-due\" name=\"due\" type=\"datetime-local\"></div><div class=\"form-column\"></div></div><div class=\"form-button-container\"><button id=\"edit-save\" class=\"form-save-button button\" type=\"submit\">Save</button> <a class=\"form-close-button button\" href=\"#\">Close</a></div></form></div></body></html>")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype HTML><html lang=\"en-US\" data-show-completed=\"false\"><head><title>Todo</title><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><link rel=\"stylesheet\" href=\"/css/styles.css\"><script src=\"/js/script.js\"></script><script src=\"/js/lib/htmx.min.js\"></script><!-- Font Awesome --><script src=\"https://kit.fontawesome.com/469cdddb31.js\" crossorigin=\"anonymous\"></script></head><body><nav><div id=\"nav-container\"><div id=\"nav-left\"><a id=\"new-button\" href=\"#create-item\">New</a></div><div id=\"nav-center\"></div><div id=\"nav-right\"><label for=\"show-completed\">Show completed</label> <input id=\"show-completed\" type=\"checkbox\" name=\"show-completed\"></div></div></nav><div id=\"main-content\"><div id=\"lists\"><div id=\"overdue-list\" class=\"todo-list\"><div class=\"todo-list-title\">Overdue</div><div class=\"todo-list-items\" hx-get=\"/overdue\" hx-trigger=\"load\" hx-swap=\"outerHTML\"></div><div class=\"new-item\"></div></div><div id=\"today-list\" class=\"todo-list\"><div class=\"todo-list-title\">Today</div><div class=\"todo-list-items\" hx-get=\"/today\" hx-trigger=\"load\" hx-swap=\"outerHTML\"></div><div class=\"new-item\"></div></div><div id=\"upcoming-list\" class=\"todo-list\"><div class=\"todo-list-title\">Upcoming</div><div class=\"todo-list-items\" hx-get=\"/upcoming\" hx-trigger=\"load\" hx-swap=\"outerHTML\"></div><div class=\"new-item\"></div></div></div><form id=\"create-item\" hx-post=\"/new\" hx-swap=\"none\"><div class=\"form-title\">Create new Todo</div><div class=\"form-container\"><div class=\"form-column\"><label for=\"name\">Name</label><br><input id=\"create-item-name\" type=\"text\" name=\"name\"><br><label for=\"start\">Start</label><br><input id=\"create-item-start\" name=\"start\" type=\"datetime-local\"><br><label for=\"due\">Due</label><br><input id=\"create-item-due\" name=\"due\" type=\"datetime-local\"></div><div class=\"form-column\"></div></div><div class=\"form-button-container\"><button id=\"create-save\" class=\"form-save-button button\" type=\"submit\">Save</button> <a class=\"form-close-button button\" href=\"#\">Close</a></div></form><form id=\"edit-item\" data-id=\"\" hx-put=\"/update\" hx-swap=\"outerHTML\"><div class=\"form-title\">Edit Todo</div><div class=\"form-container\"><div class=\"form-column\"><label for=\"name\">Name</label><br><input id=\"edit-item-name\" type=\"text\" name=\"name\"><br><label for=\"start\">Start</label><br><input id=\"edit-item-start\" name=\"start\" type=\"datetime-local\"><br><label for=\"due\">Due</label><br><input id=\"edit-item-due\" name=\"due\" type=\"datetime-local\"></div><div class=\"form-column\"></div></div><div class=\"form-button-container\"><button id=\"edit-save\" class=\"form-save-button button\" type=\"submit\">Save</button> <a class=\"form-close-button button\" href=\"#\">Close</a></div></form></div></body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -75,7 +75,7 @@ func TodoItem(item types.Todo) templ.Component {
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("item-%d", item.Id))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 113, Col: 45}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 116, Col: 45}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
@ -88,7 +88,7 @@ func TodoItem(item types.Todo) templ.Component {
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", item.Id))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 113, Col: 105}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 116, Col: 105}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
@ -101,7 +101,7 @@ func TodoItem(item types.Todo) templ.Component {
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", item.Start))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 113, Col: 150}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 116, Col: 150}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
@ -114,7 +114,7 @@ func TodoItem(item types.Todo) templ.Component {
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", item.Due))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 113, Col: 191}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 116, Col: 191}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
@ -137,7 +137,7 @@ func TodoItem(item types.Todo) templ.Component {
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(string(templ.URL(fmt.Sprintf("/set/%d", item.Id))))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 114, Col: 137}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 117, Col: 137}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
@ -150,7 +150,7 @@ func TodoItem(item types.Todo) templ.Component {
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(item.Text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 115, Col: 42}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 118, Col: 42}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
@ -163,7 +163,7 @@ func TodoItem(item types.Todo) templ.Component {
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/delete/%d", item.Id))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 118, Col: 101}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 121, Col: 101}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
@ -202,7 +202,7 @@ func OobTodoItem(targetSelector string, item types.Todo) templ.Component {
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s:%s", "afterend", targetSelector))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 124, Col: 71}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 127, Col: 71}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
@ -249,7 +249,7 @@ func TodoList(fillerText string, items []types.Todo) templ.Component {
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", len(items)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 130, Col: 80}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 133, Col: 80}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
@ -262,7 +262,7 @@ func TodoList(fillerText string, items []types.Todo) templ.Component {
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(fillerText)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 131, Col: 45}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 134, Col: 45}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil {

View File

@ -13,6 +13,7 @@ nav {
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
}
#new-button {
@ -107,6 +108,11 @@ nav {
gap: 10px;
}
[data-show-completed="false"]
.todo-item:has(input[type="checkbox"]:checked) {
display: none;
}
.todo-text {
flex: 1;
text-wrap: nowrap;

View File

@ -66,6 +66,11 @@ function on_load() {
overdue_items.setAttribute("data-item-count", parseInt(overdue_items.getAttribute("data-item-count")) + 1);
});
let checkbox = document.getElementById("show-completed");
checkbox.addEventListener("change", function(evt) {
document.documentElement.setAttribute("data-show-completed", checkbox.checked);
});
document.querySelector("#today-list > .new-item").addEventListener("htmx:oobBeforeSwap", function(evt) {
let today_items = document.querySelector("#today-list > .todo-list-items");
let due = parseInt(evt.detail.fragment.firstElementChild.getAttribute("data-due"));