]> git.taranathan.com Git - robotics-task-manager.git/commitdiff
task view and fix defcon parsing
authorTaran Nathan <moogoesmeow123@gmail.com>
Sun, 27 Sep 2026 04:45:46 +0000 (21:45 -0700)
committerTaran Nathan <moogoesmeow123@gmail.com>
Sun, 27 Sep 2026 05:16:16 +0000 (22:16 -0700)
client/src/client.gleam
server/src/server.gleam
server/static/client.js

index 4b8be575aee96e41c0c02cd7edd131c1c0848481..ba32883dbbf048ee35faa30d1f176b4382b2685b 100644 (file)
@@ -77,12 +77,15 @@ pub type Message {
   Nothing
   ArchiveProject(Int)
   UnarchiveProject(Int)
+  ListView
+  DashboardView
 }
 
 fn init(_flags: Option(Nil)) -> #(Model, Effect(Message)) {
   let page = case current_path() {
     "/login" -> "login"
     "/register" -> "register"
+    "/tasks" -> "tasks"
     _ -> "dashboard"
   }
 
@@ -107,7 +110,7 @@ fn init(_flags: Option(Nil)) -> #(Model, Effect(Message)) {
     )
 
   case page {
-    "dashboard" | "login" -> #(
+    "dashboard" | "tasks" | "login" -> #(
       model,
       rsvp.get(
         "/api/dashboard",
@@ -121,10 +124,7 @@ fn init(_flags: Option(Nil)) -> #(Model, Effect(Message)) {
 fn update(model: Model, message: Message) -> #(Model, Effect(Message)) {
   case message {
     DashboardLoaded(Ok(value)) -> {
-      #(
-        Model(..model, page: "dashboard", dashboard: Some(value), error: None),
-        effect.none(),
-      )
+      #(Model(..model, dashboard: Some(value), error: None), effect.none())
     }
     DashboardLoaded(Error(_)) -> {
       #(
@@ -214,8 +214,7 @@ fn update(model: Model, message: Message) -> #(Model, Effect(Message)) {
           field(fields, "due_date"),
           list.key_find(fields, "defcon")
             |> result.unwrap("")
-            |> dynamic.string
-            |> decode.run(decode.int)
+            |> int.parse
             |> result.unwrap(0),
         )
         |> shared.new_task_json
@@ -328,6 +327,8 @@ fn update(model: Model, message: Message) -> #(Model, Effect(Message)) {
       rsvp.get("/api/logout", rsvp.expect_ok_response(fn(_) { Nothing })),
     )
     Nothing -> #(model, effect.none())
+    ListView -> #(Model(..model, page: "tasks"), effect.none())
+    DashboardView -> #(Model(..model, page: "dashboard"), effect.none())
   }
 }
 
@@ -453,6 +454,7 @@ fn view(model: Model) -> Element(Message) {
         "Create account",
         RegisterSubmitted,
       )
+    "tasks" -> task_list_view(model)
     _ -> dashboard_view(model)
   }
 }
@@ -561,8 +563,9 @@ fn dashboard_view(model: Model) -> Element(Message) {
                 html.text("FRC 972 · IRON CLAW"),
                 html.h1([], [html.text("project board")]),
               ]),
-              html.button([event.on_click(LogOut)], [
-                html.text("Sign out"),
+              html.nav([], [
+                html.button([event.on_click(ListView)], [html.text("List view")]),
+                html.button([event.on_click(LogOut)], [html.text("Sign out")]),
               ]),
             ]),
             html.section([attribute.class("hero")], [
@@ -622,6 +625,198 @@ fn dashboard_view(model: Model) -> Element(Message) {
   }
 }
 
+fn task_list_view(model: Model) -> Element(Message) {
+  case model.dashboard {
+    None ->
+      html.main([attribute.class("auth")], [
+        styles(),
+        html.h1([], [html.text("Loading Iron Claw")]),
+      ])
+    Some(board) ->
+      html.main([attribute.class("page")], [
+        styles(),
+        html.header([], [
+          html.div([], [
+            html.text("FRC 972 · IRON CLAW"),
+            html.h1([], [html.text("task list")]),
+          ]),
+          html.nav([], [
+            html.button([event.on_click(DashboardView)], [
+              html.text("Board view"),
+            ]),
+            html.button([event.on_click(LogOut)], [html.text("Sign out")]),
+          ]),
+        ]),
+        html.section([attribute.class("panel")], [
+          html.h2([], [html.text("Find tasks")]),
+          html.input([
+            attribute.class("task-search"),
+            attribute.styles([
+              #("width", "100%"),
+              #("box-sizing", "border-box"),
+            ]),
+            attribute.placeholder("Search owner or assignees"),
+            event.on_input(TaskSearchChanged),
+          ]),
+          html.select(
+            [
+              attribute.class("subteam-filter"),
+              attribute.styles([
+                #("width", "100%"),
+                #("margin-top", "8px"),
+                #("box-sizing", "border-box"),
+                #("padding", "12px 13px"),
+                #("border", "1px solid #d9cfc2"),
+                #("border-radius", "0"),
+                #("background", "#fffcf8"),
+                #("color", "#28231e"),
+                #("font", "inherit"),
+                #("appearance", "auto"),
+              ]),
+              event.on_change(SubteamFilterChanged),
+            ],
+            [
+              html.option([attribute.value("all")], "All subteams"),
+              html.option([attribute.value("none")], "No subteam"),
+              html.option([attribute.value("electrical")], "Electrical"),
+              html.option([attribute.value("software")], "Software"),
+              html.option([attribute.value("CAD")], "CAD"),
+              html.option([attribute.value("machining")], "Machining"),
+              html.option([attribute.value("operations")], "Operations"),
+            ],
+          ),
+        ]),
+        html.section(
+          [
+            attribute.class("panel table-panel"),
+            attribute.styles([#("padding", "0")]),
+          ],
+          [
+            html.div(
+              [
+                attribute.class("task-table-scroll"),
+                attribute.styles([#("overflow-x", "auto")]),
+              ],
+              [
+                html.table(
+                  [
+                    attribute.styles([
+                      #("width", "100%"),
+                      #("border-collapse", "collapse"),
+                      #("min-width", "760px"),
+                    ]),
+                  ],
+                  [
+                    html.thead([], [
+                      html.tr([], [
+                        table_heading("Task"),
+                        table_heading("Project"),
+                        table_heading("Owner"),
+                        table_heading("Assignees"),
+                        table_heading("Subteams"),
+                        table_heading("Due"),
+                        table_heading("DEFCON"),
+                        table_heading("Status"),
+                      ]),
+                    ]),
+                    html.tbody(
+                      [],
+                      list.map(
+                        task_rows(board.projects, model)
+                          |> list.sort(fn(a, b) {
+                            case int.compare(b.1.defcon, a.1.defcon) {
+                              order.Eq ->
+                                string.compare(a.1.due_date, b.1.due_date)
+                              x -> x
+                            }
+                          }),
+                        task_row,
+                      ),
+                    ),
+                  ],
+                ),
+              ],
+            ),
+          ],
+        ),
+      ])
+  }
+}
+
+fn table_heading(label: String) -> Element(Message) {
+  html.th(
+    [
+      attribute.styles([
+        #("padding", "13px 15px"),
+        #("background", "#f0ebe3"),
+        #("border-bottom", "1px solid #d9cfc2"),
+        #("color", "#796f64"),
+        #("font-size", ".72rem"),
+        #("letter-spacing", ".06em"),
+        #("text-align", "left"),
+        #("text-transform", "uppercase"),
+        #("white-space", "nowrap"),
+      ]),
+    ],
+    [html.text(label)],
+  )
+}
+
+fn task_rows(
+  projects: List(shared.Project),
+  model: Model,
+) -> List(#(String, shared.Task)) {
+  projects
+  |> list.flat_map(fn(project) {
+    project.tasks
+    |> list.filter(fn(task) { task_matches(model, task) })
+    |> list.map(fn(task) { #(project.name, task) })
+  })
+}
+
+fn task_row(row: #(String, shared.Task)) -> Element(Message) {
+  let #(project_name, task) = row
+  html.tr([], [
+    table_cell([html.strong([], [html.text(task.title)])]),
+    table_cell([html.text(project_name)]),
+    table_cell([html.text(if_blank(task.assignee, "Unassigned"))]),
+    table_cell([html.text(if_blank(task.assignees, "None"))]),
+    table_cell([html.text(if_blank(join_subteams(task.subteams), "None"))]),
+    table_cell([
+      html.text(case task.due_date {
+        "" -> "No due date"
+        date -> date
+      }),
+    ]),
+    table_cell([
+      html.span([attribute.class("task-status")], [
+        html.text(int.to_string(task.defcon)),
+      ]),
+    ]),
+    table_cell([
+      html.span([attribute.class("task-status")], [
+        html.text(task.status),
+      ]),
+    ]),
+  ])
+}
+
+fn table_cell(children: List(Element(Message))) -> Element(Message) {
+  html.td(
+    [
+      attribute.styles([
+        #("padding", "13px 15px"),
+        #("border-bottom", "1px solid #eee6dc"),
+        #("color", "#4e463e"),
+        #("font-size", ".82rem"),
+        #("text-align", "left"),
+        #("vertical-align", "top"),
+      ]),
+    ],
+    children,
+  )
+}
+
 fn project_sections(
   model: Model,
   projects: List(shared.Project),
@@ -754,7 +949,7 @@ fn kanban_column(title: String, tasks: List(shared.Task)) -> Element(Message) {
       ],
       tasks
         |> list.sort(fn(a, b) {
-          case int.compare(a.defcon, b.defcon) {
+          case int.compare(b.defcon, a.defcon) {
             order.Eq -> string.compare(a.due_date, b.due_date)
             a -> a
           }
index 4014b498443bdb052599b7fc0c0912c15a40ac99..fc519340eb115dc81265bdfeb2b44d02de0d804d 100644 (file)
@@ -76,6 +76,8 @@ fn handle_request(db: Database, request: Request) -> Response {
     http.Get, [] -> shell()
     http.Get, ["login"] -> shell()
     http.Get, ["register"] -> shell()
+    http.Get, ["tasks"] -> shell()
+    http.Get, ["dashboard"] -> shell()
     http.Get, ["api", "dashboard"] -> api_dashboard(db, req)
     http.Get, ["api", "logout"] -> logout(req)
     http.Post, ["api", "login"] ->
index f92bb977386af363e7e33d7abc7b5c011215232d..5b06f97dc08e7b879b679e234f2d7578b6b3d34a 100644 (file)
@@ -917,6 +917,26 @@ function append(first, second) {
 function prepend2(list, item) {
   return prepend(item, list);
 }
+function flatten_loop(loop$lists, loop$acc) {
+  while (true) {
+    let lists = loop$lists;
+    let acc = loop$acc;
+    if (lists instanceof Empty) {
+      return reverse(acc);
+    } else {
+      let list = lists.head;
+      let further_lists = lists.tail;
+      loop$lists = further_lists;
+      loop$acc = reverse_and_prepend(list, acc);
+    }
+  }
+}
+function flatten(lists) {
+  return flatten_loop(lists, List$Empty$const);
+}
+function flat_map(list, fun) {
+  return flatten(map2(list, fun));
+}
 function fold2(loop$list, loop$initial, loop$fun) {
   while (true) {
     let list = loop$list;
@@ -1544,9 +1564,17 @@ function decode_bool(data) {
 }
 
 // build/dev/javascript/gleam_stdlib/gleam_stdlib.mjs
+var Nil = undefined;
 function identity(x) {
   return x;
 }
+function parse_int(value) {
+  if (/^[-+]?(\d+)$/.test(value)) {
+    return Result$Ok(parseInt(value));
+  } else {
+    return Result$Error(Nil);
+  }
+}
 function to_string(term) {
   return term.toString();
 }
@@ -1898,6 +1926,21 @@ function isResult(data) {
   return Result$isOk(data) || Result$isError(data);
 }
 
+// build/dev/javascript/gleam_stdlib/gleam/int.mjs
+function compare2(a, b) {
+  let $ = a === b;
+  if ($) {
+    return Order$Eq$const;
+  } else {
+    let $1 = a < b;
+    if ($1) {
+      return Order$Lt$const;
+    } else {
+      return Order$Gt$const;
+    }
+  }
+}
+
 // build/dev/javascript/gleam_stdlib/gleam/string_tree.mjs
 class All extends CustomType {
 }
@@ -1911,7 +1954,7 @@ var Direction$Leading$const = new Leading;
 class Trailing extends CustomType {
 }
 var Direction$Trailing$const = new Trailing;
-function compare2(a, b) {
+function compare3(a, b) {
   let $ = a === b;
   if ($) {
     return Order$Eq$const;
@@ -2283,7 +2326,7 @@ function singleton_list(item) {
 var GT = /* @__PURE__ */ Order$Gt();
 var LT = /* @__PURE__ */ Order$Lt();
 var EQ = /* @__PURE__ */ Order$Eq();
-function compare3(a, b) {
+function compare4(a, b) {
   if (a.name === b.name) {
     return EQ;
   } else if (a.name < b.name) {
@@ -2478,7 +2521,7 @@ function prepare(attributes) {
     } else {
       let _pipe = attributes;
       let _pipe$1 = sort(_pipe, (a, b) => {
-        return compare3(b, a);
+        return compare4(b, a);
       });
       return merge(_pipe$1, empty_list);
     }
@@ -2886,6 +2929,9 @@ function h3(attrs, children) {
 function main(attrs, children) {
   return element2("main", attrs, children);
 }
+function nav(attrs, children) {
+  return element2("nav", attrs, children);
+}
 function section(attrs, children) {
   return element2("section", attrs, children);
 }
@@ -2907,6 +2953,24 @@ function span(attrs, children) {
 function strong(attrs, children) {
   return element2("strong", attrs, children);
 }
+function table(attrs, children) {
+  return element2("table", attrs, children);
+}
+function tbody(attrs, children) {
+  return element2("tbody", attrs, children);
+}
+function td(attrs, children) {
+  return element2("td", attrs, children);
+}
+function th(attrs, children) {
+  return element2("th", attrs, children);
+}
+function thead(attrs, children) {
+  return element2("thead", attrs, children);
+}
+function tr(attrs, children) {
+  return element2("tr", attrs, children);
+}
 function button(attrs, children) {
   return element2("button", attrs, children);
 }
@@ -3971,7 +4035,7 @@ function diff_attributes(loop$controlled, loop$path, loop$events, loop$old, loop
       let remaining_old = old.tail;
       let next = new$5.head;
       let remaining_new = new$5.tail;
-      let $ = compare3(prev, next);
+      let $ = compare4(prev, next);
       if ($ instanceof Lt) {
         if (prev instanceof Event2) {
           let name2 = prev.name;
@@ -7438,6 +7502,12 @@ class UnarchiveProject extends CustomType {
     this[0] = $0;
   }
 }
+class ListView extends CustomType {
+}
+var Message$ListView$const = new ListView;
+class DashboardView extends CustomType {
+}
+var Message$DashboardView$const = new DashboardView;
 function subteam_checkbox(selected, value3, label2) {
   return label(toList([
     class$("subteam-option"),
@@ -7590,7 +7660,12 @@ function kanban_column(title2, tasks) {
   ]), (() => {
     let _pipe = tasks;
     let _pipe$1 = sort(_pipe, (a2, b) => {
-      return compare2(a2.due_date, b.due_date);
+      let $ = compare2(b.defcon, a2.defcon);
+      if ($ instanceof Eq) {
+        return compare3(a2.due_date, b.due_date);
+      } else {
+        return $;
+      }
     });
     return map2(_pipe$1, task_view);
   })()));
@@ -7732,7 +7807,10 @@ function dashboard_view(model) {
           text3("FRC 972 · IRON CLAW"),
           h1(List$Empty$const, toList([text3("project board")]))
         ])),
-        button(toList([on_click(Message$LogOut$const)]), toList([text3("Sign out")]))
+        nav(List$Empty$const, toList([
+          button(toList([on_click(Message$ListView$const)]), toList([text3("List view")])),
+          button(toList([on_click(Message$LogOut$const)]), toList([text3("Sign out")]))
+        ]))
       ])),
       section(toList([class$("hero")]), toList([text3("wassup")])),
       section(toList([class$("panel")]), toList([
@@ -7792,6 +7870,176 @@ function dashboard_view(model) {
     ]));
   }
 }
+function table_cell(children) {
+  return td(toList([
+    styles(toList([
+      ["padding", "13px 15px"],
+      ["border-bottom", "1px solid #eee6dc"],
+      ["color", "#4e463e"],
+      ["font-size", ".82rem"],
+      ["text-align", "left"],
+      ["vertical-align", "top"]
+    ]))
+  ]), children);
+}
+function task_row(row) {
+  let project_name = row[0];
+  let task = row[1];
+  return tr(List$Empty$const, toList([
+    table_cell(toList([
+      strong(List$Empty$const, toList([text3(task.title)]))
+    ])),
+    table_cell(toList([text3(project_name)])),
+    table_cell(toList([text3(if_blank(task.assignee, "Unassigned"))])),
+    table_cell(toList([text3(if_blank(task.assignees, "None"))])),
+    table_cell(toList([text3(if_blank(join_subteams(task.subteams), "None"))])),
+    table_cell(toList([
+      text3((() => {
+        let $ = task.due_date;
+        if ($ === "") {
+          return "No due date";
+        } else {
+          return $;
+        }
+      })())
+    ])),
+    table_cell(toList([
+      span(toList([class$("task-status")]), toList([text3(to_string(task.defcon))]))
+    ])),
+    table_cell(toList([
+      span(toList([class$("task-status")]), toList([text3(task.status)]))
+    ]))
+  ]));
+}
+function task_rows(projects, model) {
+  let _pipe = projects;
+  return flat_map(_pipe, (project) => {
+    let _pipe$1 = project.tasks;
+    let _pipe$2 = filter(_pipe$1, (task) => {
+      return task_matches(model, task);
+    });
+    return map2(_pipe$2, (task) => {
+      return [project.name, task];
+    });
+  });
+}
+function table_heading(label2) {
+  return th(toList([
+    styles(toList([
+      ["padding", "13px 15px"],
+      ["background", "#f0ebe3"],
+      ["border-bottom", "1px solid #d9cfc2"],
+      ["color", "#796f64"],
+      ["font-size", ".72rem"],
+      ["letter-spacing", ".06em"],
+      ["text-align", "left"],
+      ["text-transform", "uppercase"],
+      ["white-space", "nowrap"]
+    ]))
+  ]), toList([text3(label2)]));
+}
+function task_list_view(model) {
+  let $ = model.dashboard;
+  if ($ instanceof Some) {
+    let board = $[0];
+    return main(toList([class$("page")]), toList([
+      styles2(),
+      header(List$Empty$const, toList([
+        div(List$Empty$const, toList([
+          text3("FRC 972 · IRON CLAW"),
+          h1(List$Empty$const, toList([text3("task list")]))
+        ])),
+        nav(List$Empty$const, toList([
+          button(toList([on_click(Message$DashboardView$const)]), toList([text3("Board view")])),
+          button(toList([on_click(Message$LogOut$const)]), toList([text3("Sign out")]))
+        ]))
+      ])),
+      section(toList([class$("panel")]), toList([
+        h2(List$Empty$const, toList([text3("Find tasks")])),
+        input(toList([
+          class$("task-search"),
+          styles(toList([["width", "100%"], ["box-sizing", "border-box"]])),
+          placeholder("Search owner or assignees"),
+          on_input((var0) => {
+            return new TaskSearchChanged(var0);
+          })
+        ])),
+        select(toList([
+          class$("subteam-filter"),
+          styles(toList([
+            ["width", "100%"],
+            ["margin-top", "8px"],
+            ["box-sizing", "border-box"],
+            ["padding", "12px 13px"],
+            ["border", "1px solid #d9cfc2"],
+            ["border-radius", "0"],
+            ["background", "#fffcf8"],
+            ["color", "#28231e"],
+            ["font", "inherit"],
+            ["appearance", "auto"]
+          ])),
+          on_change((var0) => {
+            return new SubteamFilterChanged(var0);
+          })
+        ]), toList([
+          option(toList([value("all")]), "All subteams"),
+          option(toList([value("none")]), "No subteam"),
+          option(toList([value("electrical")]), "Electrical"),
+          option(toList([value("software")]), "Software"),
+          option(toList([value("CAD")]), "CAD"),
+          option(toList([value("machining")]), "Machining"),
+          option(toList([value("operations")]), "Operations")
+        ]))
+      ])),
+      section(toList([
+        class$("panel table-panel"),
+        styles(toList([["padding", "0"]]))
+      ]), toList([
+        div(toList([
+          class$("task-table-scroll"),
+          styles(toList([["overflow-x", "auto"]]))
+        ]), toList([
+          table(toList([
+            styles(toList([
+              ["width", "100%"],
+              ["border-collapse", "collapse"],
+              ["min-width", "760px"]
+            ]))
+          ]), toList([
+            thead(List$Empty$const, toList([
+              tr(List$Empty$const, toList([
+                table_heading("Task"),
+                table_heading("Project"),
+                table_heading("Owner"),
+                table_heading("Assignees"),
+                table_heading("Subteams"),
+                table_heading("Due"),
+                table_heading("DEFCON"),
+                table_heading("Status")
+              ]))
+            ])),
+            tbody(List$Empty$const, map2((() => {
+              let _pipe = task_rows(board.projects, model);
+              return sort(_pipe, (a2, b) => {
+                let $1 = compare2(b[1].defcon, a2[1].defcon);
+                if ($1 instanceof Eq) {
+                  return compare3(a2[1].due_date, b[1].due_date);
+                } else {
+                  return $1;
+                }
+              });
+            })(), task_row))
+          ]))
+        ]))
+      ]))
+    ]));
+  } else {
+    return main(toList([class$("auth")]), toList([
+      styles2(),
+      h1(List$Empty$const, toList([text3("Loading Iron Claw")]))
+    ]));
+  }
+}
 function creat_account_view(model, title2, submit, on_submit2) {
   let _block;
   let $ = submit === "Log in";
@@ -7900,6 +8148,8 @@ function view(model) {
     return creat_account_view(model, "Create account", "Create account", (var0) => {
       return new RegisterSubmitted(var0);
     });
+  } else if ($ === "tasks") {
+    return task_list_view(model);
   } else {
     return dashboard_view(model);
   }
@@ -8003,7 +8253,7 @@ function update2(model, message2) {
     if ($ instanceof Ok) {
       let value3 = $[0];
       return [
-        new Model("dashboard", new Some(value3), Option$None$const, model.username, model.password, model.invite_password, model.project_name, model.project_description, model.task_title, model.task_description, model.task_assignee, model.task_assignees, model.task_subteams, model.task_due_date, model.subteam_filter, model.task_search),
+        new Model(model.page, new Some(value3), Option$None$const, model.username, model.password, model.invite_password, model.project_name, model.project_description, model.task_title, model.task_description, model.task_assignee, model.task_assignees, model.task_subteams, model.task_due_date, model.subteam_filter, model.task_search),
         none()
       ];
     } else {
@@ -8042,9 +8292,8 @@ function update2(model, message2) {
     let _pipe = new NewTask(field2(fields, "title"), field2(fields, "description"), field2(fields, "assignee"), field2(fields, "assignees"), model.task_subteams, field2(fields, "due_date"), (() => {
       let _pipe2 = key_find(fields, "defcon");
       let _pipe$1 = unwrap2(_pipe2, "");
-      let _pipe$2 = identity(_pipe$1);
-      let _pipe$3 = run(_pipe$2, int2);
-      return unwrap2(_pipe$3, 0);
+      let _pipe$2 = parse_int(_pipe$1);
+      return unwrap2(_pipe$2, 0);
     })());
     _block = new_task_json(_pipe);
     let body2 = _block;
@@ -8235,7 +8484,7 @@ function update2(model, message2) {
         })());
       }))
     ];
-  } else {
+  } else if (message2 instanceof UnarchiveProject) {
     let project_id = message2[0];
     return [
       model,
@@ -8248,6 +8497,16 @@ function update2(model, message2) {
         })());
       }))
     ];
+  } else if (message2 instanceof ListView) {
+    return [
+      new Model("tasks", model.dashboard, model.error, model.username, model.password, model.invite_password, model.project_name, model.project_description, model.task_title, model.task_description, model.task_assignee, model.task_assignees, model.task_subteams, model.task_due_date, model.subteam_filter, model.task_search),
+      none()
+    ];
+  } else {
+    return [
+      new Model("dashboard", model.dashboard, model.error, model.username, model.password, model.invite_password, model.project_name, model.project_description, model.task_title, model.task_description, model.task_assignee, model.task_assignees, model.task_subteams, model.task_due_date, model.subteam_filter, model.task_search),
+      none()
+    ];
   }
 }
 function current_path() {
@@ -8262,6 +8521,8 @@ function init(_) {
     _block = "login";
   } else if ($ === "/register") {
     _block = "register";
+  } else if ($ === "/tasks") {
+    _block = "tasks";
   } else {
     _block = "dashboard";
   }
@@ -8274,6 +8535,13 @@ function init(_) {
         return new DashboardLoaded(var0);
       }))
     ];
+  } else if (page === "tasks") {
+    return [
+      model,
+      get3("/api/dashboard", expect_json(dashboard_decoder(), (var0) => {
+        return new DashboardLoaded(var0);
+      }))
+    ];
   } else if (page === "login") {
     return [
       model,
@@ -8289,7 +8557,7 @@ function main2() {
   let app = application(init, update2, view);
   let $ = start4(app, "#app", Option$None$const);
   if (!($ instanceof Ok)) {
-    throw makeError("let_assert", FILEPATH, "client", 22, "main", "Pattern match failed, no pattern matched the value.", { value: $, start: 508, end: 558, pattern_start: 519, pattern_end: 524 });
+    throw makeError("let_assert", FILEPATH, "client", 23, "main", "Pattern match failed, no pattern matched the value.", { value: $, start: 527, end: 577, pattern_start: 538, pattern_end: 543 });
   }
   return;
 }