]> git.taranathan.com Git - robotics-task-manager.git/commitdiff
calendar view main
authorTaran Nathan <moogoesmeow123@gmail.com>
Mon, 28 Sep 2026 00:37:42 +0000 (17:37 -0700)
committerTaran Nathan <moogoesmeow123@gmail.com>
Mon, 28 Sep 2026 02:37:19 +0000 (19:37 -0700)
client/src/client.gleam
server/static/client.js

index a3a415b5c906936ff909e454c8fc6b848344b54d..f56f2dcbb1a637a0e8835ca08d33d7702cb761c3 100644 (file)
@@ -1,5 +1,3 @@
-import gleam/dynamic
-import gleam/dynamic/decode
 import gleam/int
 import gleam/json
 import gleam/list
@@ -15,6 +13,7 @@ import lustre/element/html
 import lustre/event
 import plinth/browser/location
 import plinth/browser/window
+import plinth/javascript/date
 import rsvp
 import shared
 
@@ -46,6 +45,7 @@ type Model {
     task_due_date: String,
     subteam_filter: String,
     task_search: String,
+    calendar_week: Int,
   )
 }
 
@@ -79,6 +79,9 @@ pub type Message {
   UnarchiveProject(Int)
   ListView
   DashboardView
+  CalendarView
+  PreviousWeek
+  NextWeek
 }
 
 fn init(_flags: Option(Nil)) -> #(Model, Effect(Message)) {
@@ -86,6 +89,7 @@ fn init(_flags: Option(Nil)) -> #(Model, Effect(Message)) {
     "/login" -> "login"
     "/register" -> "register"
     "/tasks" -> "tasks"
+    "/calendar" -> "calendar"
     _ -> "dashboard"
   }
 
@@ -107,10 +111,11 @@ fn init(_flags: Option(Nil)) -> #(Model, Effect(Message)) {
       task_due_date: "",
       subteam_filter: "all",
       task_search: "",
+      calendar_week: 0,
     )
 
   case page {
-    "dashboard" | "tasks" | "login" -> #(
+    "dashboard" | "tasks" | "calendar" | "login" -> #(
       model,
       rsvp.get(
         "/api/dashboard",
@@ -329,6 +334,15 @@ fn update(model: Model, message: Message) -> #(Model, Effect(Message)) {
     Nothing -> #(model, effect.none())
     ListView -> #(Model(..model, page: "tasks"), effect.none())
     DashboardView -> #(Model(..model, page: "dashboard"), effect.none())
+    CalendarView -> #(Model(..model, page: "calendar"), effect.none())
+    PreviousWeek -> #(
+      Model(..model, calendar_week: model.calendar_week - 1),
+      effect.none(),
+    )
+    NextWeek -> #(
+      Model(..model, calendar_week: model.calendar_week + 1),
+      effect.none(),
+    )
   }
 }
 
@@ -455,6 +469,7 @@ fn view(model: Model) -> Element(Message) {
         RegisterSubmitted,
       )
     "tasks" -> task_list_view(model)
+    "calendar" -> calendar_view(model)
     _ -> dashboard_view(model)
   }
 }
@@ -565,6 +580,9 @@ fn dashboard_view(model: Model) -> Element(Message) {
               ]),
               html.nav([], [
                 html.button([event.on_click(ListView)], [html.text("List view")]),
+                html.button([event.on_click(CalendarView)], [
+                  html.text("Calendar"),
+                ]),
                 html.button([event.on_click(LogOut)], [html.text("Sign out")]),
               ]),
             ]),
@@ -644,6 +662,7 @@ fn task_list_view(model: Model) -> Element(Message) {
             html.button([event.on_click(DashboardView)], [
               html.text("Board view"),
             ]),
+            html.button([event.on_click(CalendarView)], [html.text("Calendar")]),
             html.button([event.on_click(LogOut)], [html.text("Sign out")]),
           ]),
         ]),
@@ -817,6 +836,313 @@ fn table_cell(children: List(Element(Message))) -> Element(Message) {
   )
 }
 
+fn calendar_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("calendar")]),
+          ]),
+          html.nav([], [
+            html.button([event.on_click(DashboardView)], [
+              html.text("Board view"),
+            ]),
+            html.button([event.on_click(ListView)], [html.text("List 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.placeholder("Search owner or assignees"),
+            event.on_input(TaskSearchChanged),
+            attribute.styles([
+              #("width", "100%"),
+              #("box-sizing", "border-box"),
+            ]),
+          ]),
+          html.select(
+            [
+              attribute.class("subteam-filter"),
+              event.on_change(SubteamFilterChanged),
+              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"),
+              ]),
+            ],
+            [
+              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"),
+            attribute.styles([#("padding", "0"), #("overflow", "hidden")]),
+          ],
+          [
+            html.div(
+              [
+                attribute.styles([
+                  #("display", "flex"),
+                  #("justify-content", "space-between"),
+                  #("align-items", "center"),
+                  #("padding", "14px 16px"),
+                  #("border-bottom", "1px solid #e2d9cc"),
+                ]),
+              ],
+              [
+                html.button([event.on_click(PreviousWeek)], [
+                  html.text("← Previous week"),
+                ]),
+                html.strong([], [html.text(week_label(model.calendar_week))]),
+                html.button([event.on_click(NextWeek)], [
+                  html.text("Next week →"),
+                ]),
+              ],
+            ),
+            html.div(
+              [
+                attribute.class("calendar-grid"),
+                attribute.styles([
+                  #("display", "grid"),
+                  #("grid-template-columns", "repeat(7, minmax(150px, 1fr))"),
+                  #("overflow-x", "auto"),
+                ]),
+              ],
+              list.map(current_week_dates(model.calendar_week), fn(x) {
+                let #(day_no_year, day, day_of_week) = x
+                calendar_day(
+                  day_no_year,
+                  day_of_week,
+                  calendar_tasks(board.projects, model, day),
+                )
+              }),
+            ),
+          ],
+        ),
+      ])
+  }
+}
+
+fn current_week_dates(week_offset: Int) -> List(#(String, String, String)) {
+  let today = date.now()
+  let days_from_monday = case date.day(today) {
+    0 -> 6
+    day -> day - 1
+  }
+  let current_week_start =
+    subtract_days(
+      date.year(today),
+      date.month(today) + 1,
+      date.date(today),
+      days_from_monday,
+    )
+  let start = shift_week(current_week_start, week_offset)
+
+  [0, 1, 2, 3, 4, 5, 6]
+  |> list.map(fn(offset) {
+    #(
+      format_date_no_year(add_days(start, offset)),
+      format_date(add_days(start, offset)),
+      offset |> num_to_day,
+    )
+  })
+}
+
+fn shift_week(date_value: #(Int, Int, Int), weeks: Int) -> #(Int, Int, Int) {
+  case weeks {
+    0 -> date_value
+    value if value > 0 -> add_days(date_value, value * 7)
+    value -> subtract_date_days(date_value, 0 - value * 7)
+  }
+}
+
+fn subtract_date_days(
+  date_value: #(Int, Int, Int),
+  days: Int,
+) -> #(Int, Int, Int) {
+  let #(year, month, day) = date_value
+  subtract_days(year, month, day, days)
+}
+
+fn week_label(week_offset: Int) -> String {
+  let dates = current_week_dates(week_offset)
+  let first = list.first(dates) |> result.unwrap(#("", "", ""))
+  let last = list.last(dates) |> result.unwrap(#("", "", ""))
+  first.1 <> " – " <> last.1
+}
+
+fn num_to_day(day: Int) -> String {
+  case day {
+    0 -> "Mon"
+    1 -> "Tue"
+    2 -> "Wed"
+    3 -> "Thu"
+    4 -> "Fri"
+    5 -> "Sat"
+    6 -> "Sun"
+    _ -> ""
+  }
+}
+
+fn subtract_days(
+  year: Int,
+  month: Int,
+  day: Int,
+  count: Int,
+) -> #(Int, Int, Int) {
+  case count {
+    0 -> #(year, month, day)
+    _ -> {
+      let #(previous_year, previous_month, previous_day) = case day {
+        1 -> {
+          let month = case month {
+            1 -> 12
+            _ -> month - 1
+          }
+          let year = case month == 12 {
+            True -> year - 1
+            False -> year
+          }
+          #(year, month, days_in_month(year, month))
+        }
+        _ -> #(year, month, day - 1)
+      }
+      subtract_days(previous_year, previous_month, previous_day, count - 1)
+    }
+  }
+}
+
+fn add_days(date_value: #(Int, Int, Int), count: Int) -> #(Int, Int, Int) {
+  case count {
+    0 -> date_value
+    _ -> {
+      let #(year, month, day) = date_value
+      let #(next_year, next_month, next_day) = case
+        day == days_in_month(year, month)
+      {
+        True ->
+          case month == 12 {
+            True -> #(year + 1, 1, 1)
+            False -> #(year, month + 1, 1)
+          }
+        False -> #(year, month, day + 1)
+      }
+      add_days(#(next_year, next_month, next_day), count - 1)
+    }
+  }
+}
+
+fn days_in_month(year: Int, month: Int) -> Int {
+  case month {
+    2 -> {
+      let divisible_by_400 = int.remainder(year, by: 400) == Ok(0)
+      let divisible_by_4 = int.remainder(year, by: 4) == Ok(0)
+      let divisible_by_100 = int.remainder(year, by: 100) == Ok(0)
+      case divisible_by_400 || divisible_by_4 && !divisible_by_100 {
+        True -> 29
+        False -> 28
+      }
+    }
+    4 | 6 | 9 | 11 -> 30
+    _ -> 31
+  }
+}
+
+fn format_date_no_year(date_value: #(Int, Int, Int)) -> String {
+  let #(_, month, day) = date_value
+  two_digits(month) <> "-" <> two_digits(day)
+}
+
+fn format_date(date_value: #(Int, Int, Int)) -> String {
+  let #(year, month, day) = date_value
+  int.to_string(year) <> "-" <> two_digits(month) <> "-" <> two_digits(day)
+}
+
+fn two_digits(value: Int) -> String {
+  let text = int.to_string(value)
+  case string.length(text) {
+    1 -> "0" <> text
+    _ -> text
+  }
+}
+
+fn calendar_tasks(
+  projects: List(shared.Project),
+  model: Model,
+  date: String,
+) -> List(#(String, shared.Task)) {
+  task_rows(projects, model)
+  |> list.filter(fn(row) { row.1.due_date == date })
+}
+
+fn calendar_day(
+  date: String,
+  day_of_week: String,
+  tasks: List(#(String, shared.Task)),
+) -> Element(Message) {
+  html.div(
+    [
+      attribute.styles([
+        #("min-height", "260px"),
+        #("padding", "14px"),
+        #("border-right", "1px solid #e2d9cc"),
+        #("background", "#fff"),
+      ]),
+    ],
+    [
+      html.h3([attribute.style("margin-bottom", "1px")], [
+        html.text(day_of_week),
+      ]),
+      html.h3([attribute.style("margin-top", "0px")], [
+        html.text(date),
+      ]),
+      html.div(
+        [],
+        list.map(tasks, fn(row) {
+          let #(project, task) = row
+          html.div([attribute.class("task")], [
+            html.strong([], [html.text(task.title)]),
+            html.small([], [
+              html.strong([], [html.text("Project: ")]),
+              html.text(project),
+            ]),
+            html.span([], [
+              html.strong([], [
+                html.text("DEFCON " <> int.to_string(task.defcon)),
+              ]),
+            ]),
+          ])
+        }),
+      ),
+    ],
+  )
+}
+
 fn project_sections(
   model: Model,
   projects: List(shared.Project),
index 9b76bcecfbce90fcd8c2a9322e7fadc55fac41d2..a51fe1df63e6755ba74bcca03840f8e63ee42513 100644 (file)
@@ -277,6 +277,13 @@ function structurallyCompatibleObjects(a, b) {
     return false;
   return a.constructor === b.constructor;
 }
+function remainderInt(a, b) {
+  if (b === 0) {
+    return 0;
+  } else {
+    return a % b;
+  }
+}
 function makeError(variant, file, module, line, fn, message, extra) {
   let error = new globalThis.Error(message);
   error.gleam_error = variant;
@@ -823,6 +830,14 @@ function contains(loop$list, loop$elem) {
     }
   }
 }
+function first(list) {
+  if (list instanceof Empty) {
+    return new Error2(undefined);
+  } else {
+    let first$1 = list.head;
+    return new Ok(first$1);
+  }
+}
 function filter_loop(loop$list, loop$fun, loop$acc) {
   while (true) {
     let list = loop$list;
@@ -899,20 +914,20 @@ function map2(list, fun) {
 }
 function append_loop(loop$first, loop$second) {
   while (true) {
-    let first = loop$first;
+    let first2 = loop$first;
     let second = loop$second;
-    if (first instanceof Empty) {
+    if (first2 instanceof Empty) {
       return second;
     } else {
-      let first$1 = first.head;
-      let rest$1 = first.tail;
+      let first$1 = first2.head;
+      let rest$1 = first2.tail;
       loop$first = rest$1;
       loop$second = prepend(first$1, second);
     }
   }
 }
-function append(first, second) {
-  return append_loop(reverse(first), second);
+function append(first2, second) {
+  return append_loop(reverse(first2), second);
 }
 function prepend2(list, item) {
   return prepend(item, list);
@@ -1342,6 +1357,23 @@ function each(loop$list, loop$f) {
     }
   }
 }
+function last(loop$list) {
+  while (true) {
+    let list = loop$list;
+    if (list instanceof Empty) {
+      return new Error2(undefined);
+    } else {
+      let $ = list.tail;
+      if ($ instanceof Empty) {
+        let last$1 = list.head;
+        return new Ok(last$1);
+      } else {
+        let rest$1 = $;
+        loop$list = rest$1;
+      }
+    }
+  }
+}
 
 // build/dev/javascript/gleam_stdlib/gleam/dynamic/decode.mjs
 class DecodeError extends CustomType {
@@ -1426,9 +1458,9 @@ function run_decoders(loop$data, loop$failure, loop$decoders) {
     }
   }
 }
-function one_of(first, alternatives) {
+function one_of(first2, alternatives) {
   return new Decoder((dynamic_data) => {
-    let $ = first.function(dynamic_data);
+    let $ = first2.function(dynamic_data);
     let layer = $;
     let errors = $[1];
     if (errors instanceof Empty) {
@@ -1578,6 +1610,21 @@ function parse_int(value) {
 function to_string(term) {
   return term.toString();
 }
+function string_length(string4) {
+  if (string4 === "") {
+    return 0;
+  }
+  const iterator = graphemes_iterator(string4);
+  if (iterator) {
+    let i = 0;
+    for (const _ of iterator) {
+      i++;
+    }
+    return i;
+  } else {
+    return string4.match(/./gsu).length;
+  }
+}
 function graphemes(string4) {
   const iterator = graphemes_iterator(string4);
   if (iterator) {
@@ -1747,11 +1794,11 @@ class Inspector {
   }
   #dict(map4) {
     let body = "dict.from_list([";
-    let first = true;
+    let first2 = true;
     body = fold(map4, body, (body2, key, value) => {
-      if (!first)
+      if (!first2)
         body2 = body2 + ", ";
-      first = false;
+      first2 = false;
       return body2 + "#(" + this.inspect(key) + ", " + this.inspect(value) + ")";
     });
     return body + "])";
@@ -1940,6 +1987,14 @@ function compare2(a, b) {
     }
   }
 }
+function remainder(dividend, divisor) {
+  if (divisor === 0) {
+    return new Error2(undefined);
+  } else {
+    let divisor$1 = divisor;
+    return new Ok(remainderInt(dividend, divisor$1));
+  }
+}
 
 // build/dev/javascript/gleam_stdlib/gleam/string_tree.mjs
 class All extends CustomType {
@@ -5277,8 +5332,8 @@ class Reconciler {
     const throttle = throttles.get(type);
     if (throttle) {
       const now = Date.now();
-      const last = throttle.last || 0;
-      if (now > last + throttle.delay) {
+      const last2 = throttle.last || 0;
+      if (now > last2 + throttle.delay) {
         throttle.last = now;
         throttle.lastEvent = event3;
         this.#dispatch(event3, data);
@@ -5986,8 +6041,8 @@ function start4(app, selector, arguments$) {
 }
 
 // build/dev/javascript/gleam_stdlib/gleam/pair.mjs
-function new$5(first, second) {
-  return [first, second];
+function new$5(first2, second) {
+  return [first2, second];
 }
 
 // build/dev/javascript/lustre/lustre/event.mjs
@@ -6236,6 +6291,23 @@ async function import_(string6) {
   }
 }
 
+// build/dev/javascript/plinth/date_ffi.mjs
+function now() {
+  return new Date;
+}
+function year(d) {
+  return d.getFullYear();
+}
+function month(d) {
+  return d.getMonth();
+}
+function day(d) {
+  return d.getDay();
+}
+function date(d) {
+  return d.getDate();
+}
+
 // build/dev/javascript/gleam_stdlib/gleam/uri.mjs
 class Uri extends CustomType {
   constructor(scheme, userinfo, host, port, path, query, fragment3) {
@@ -7322,7 +7394,7 @@ function status_change_json(value3) {
 var FILEPATH = "src/client.gleam";
 
 class Model extends CustomType {
-  constructor(page, dashboard, error, username, password, invite_password, project_name, project_description, task_title, task_description, task_assignee, task_assignees, task_subteams, task_due_date, subteam_filter, task_search) {
+  constructor(page, dashboard, error, username, password, invite_password, project_name, project_description, task_title, task_description, task_assignee, task_assignees, task_subteams, task_due_date, subteam_filter, task_search, calendar_week) {
     super();
     this.page = page;
     this.dashboard = dashboard;
@@ -7340,6 +7412,7 @@ class Model extends CustomType {
     this.task_due_date = task_due_date;
     this.subteam_filter = subteam_filter;
     this.task_search = task_search;
+    this.calendar_week = calendar_week;
   }
 }
 
@@ -7508,6 +7581,15 @@ var Message$ListView$const = new ListView;
 class DashboardView extends CustomType {
 }
 var Message$DashboardView$const = new DashboardView;
+class CalendarView extends CustomType {
+}
+var Message$CalendarView$const = new CalendarView;
+class PreviousWeek extends CustomType {
+}
+var Message$PreviousWeek$const = new PreviousWeek;
+class NextWeek extends CustomType {
+}
+var Message$NextWeek$const = new NextWeek;
 function subteam_checkbox(selected, value3, label2) {
   return label(toList([
     class$("subteam-option"),
@@ -7809,6 +7891,7 @@ function dashboard_view(model) {
         ])),
         nav(List$Empty$const, toList([
           button(toList([on_click(Message$ListView$const)]), toList([text3("List view")])),
+          button(toList([on_click(Message$CalendarView$const)]), toList([text3("Calendar")])),
           button(toList([on_click(Message$LogOut$const)]), toList([text3("Sign out")]))
         ]))
       ])),
@@ -7870,6 +7953,336 @@ function dashboard_view(model) {
     ]));
   }
 }
+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 calendar_tasks(projects, model, date2) {
+  let _pipe = task_rows(projects, model);
+  return filter(_pipe, (row) => {
+    return row[1].due_date === date2;
+  });
+}
+function calendar_day(date2, day_of_week, tasks) {
+  return div(toList([
+    styles(toList([
+      ["min-height", "260px"],
+      ["padding", "14px"],
+      ["border-right", "1px solid #e2d9cc"],
+      ["background", "#fff"]
+    ]))
+  ]), toList([
+    h3(toList([style("margin-bottom", "1px")]), toList([text3(day_of_week)])),
+    h3(toList([style("margin-top", "0px")]), toList([text3(date2)])),
+    div(List$Empty$const, map2(tasks, (row) => {
+      let project = row[0];
+      let task = row[1];
+      return div(toList([class$("task")]), toList([
+        strong(List$Empty$const, toList([text3(task.title)])),
+        small(List$Empty$const, toList([
+          strong(List$Empty$const, toList([text3("Project: ")])),
+          text3(project)
+        ])),
+        span(List$Empty$const, toList([
+          strong(List$Empty$const, toList([
+            text3("DEFCON " + to_string(task.defcon))
+          ]))
+        ]))
+      ]));
+    }))
+  ]));
+}
+function num_to_day(day2) {
+  if (day2 === 0) {
+    return "Mon";
+  } else if (day2 === 1) {
+    return "Tue";
+  } else if (day2 === 2) {
+    return "Wed";
+  } else if (day2 === 3) {
+    return "Thu";
+  } else if (day2 === 4) {
+    return "Fri";
+  } else if (day2 === 5) {
+    return "Sat";
+  } else if (day2 === 6) {
+    return "Sun";
+  } else {
+    return "";
+  }
+}
+function days_in_month(year2, month2) {
+  if (month2 === 2) {
+    let divisible_by_400 = isEqual(remainder(year2, 400), new Ok(0));
+    let divisible_by_4 = isEqual(remainder(year2, 4), new Ok(0));
+    let divisible_by_100 = isEqual(remainder(year2, 100), new Ok(0));
+    let $ = divisible_by_400 || divisible_by_4 && !divisible_by_100;
+    if ($) {
+      return 29;
+    } else {
+      return 28;
+    }
+  } else if (month2 === 4) {
+    return 30;
+  } else if (month2 === 6) {
+    return 30;
+  } else if (month2 === 9) {
+    return 30;
+  } else if (month2 === 11) {
+    return 30;
+  } else {
+    return 31;
+  }
+}
+function add_days(loop$date_value, loop$count) {
+  while (true) {
+    let date_value = loop$date_value;
+    let count = loop$count;
+    if (count === 0) {
+      return date_value;
+    } else {
+      let year2 = date_value[0];
+      let month2 = date_value[1];
+      let day2 = date_value[2];
+      let _block;
+      let $1 = day2 === days_in_month(year2, month2);
+      if ($1) {
+        let $2 = month2 === 12;
+        if ($2) {
+          _block = [year2 + 1, 1, 1];
+        } else {
+          _block = [year2, month2 + 1, 1];
+        }
+      } else {
+        _block = [year2, month2, day2 + 1];
+      }
+      let $ = _block;
+      let next_year = $[0];
+      let next_month = $[1];
+      let next_day = $[2];
+      loop$date_value = [next_year, next_month, next_day];
+      loop$count = count - 1;
+    }
+  }
+}
+function two_digits(value3) {
+  let text4 = to_string(value3);
+  let $ = string_length(text4);
+  if ($ === 1) {
+    return "0" + text4;
+  } else {
+    return text4;
+  }
+}
+function format_date(date_value) {
+  let year2 = date_value[0];
+  let month2 = date_value[1];
+  let day2 = date_value[2];
+  return to_string(year2) + "-" + two_digits(month2) + "-" + two_digits(day2);
+}
+function format_date_no_year(date_value) {
+  let month2 = date_value[1];
+  let day2 = date_value[2];
+  return two_digits(month2) + "-" + two_digits(day2);
+}
+function subtract_days(loop$year, loop$month, loop$day, loop$count) {
+  while (true) {
+    let year2 = loop$year;
+    let month2 = loop$month;
+    let day2 = loop$day;
+    let count = loop$count;
+    if (count === 0) {
+      return [year2, month2, day2];
+    } else {
+      let _block;
+      if (day2 === 1) {
+        let _block$1;
+        if (month2 === 1) {
+          _block$1 = 12;
+        } else {
+          _block$1 = month2 - 1;
+        }
+        let month$1 = _block$1;
+        let _block$2;
+        let $1 = month$1 === 12;
+        if ($1) {
+          _block$2 = year2 - 1;
+        } else {
+          _block$2 = year2;
+        }
+        let year$1 = _block$2;
+        _block = [year$1, month$1, days_in_month(year$1, month$1)];
+      } else {
+        _block = [year2, month2, day2 - 1];
+      }
+      let $ = _block;
+      let previous_year = $[0];
+      let previous_month = $[1];
+      let previous_day = $[2];
+      loop$year = previous_year;
+      loop$month = previous_month;
+      loop$day = previous_day;
+      loop$count = count - 1;
+    }
+  }
+}
+function subtract_date_days(date_value, days) {
+  let year2 = date_value[0];
+  let month2 = date_value[1];
+  let day2 = date_value[2];
+  return subtract_days(year2, month2, day2, days);
+}
+function shift_week(date_value, weeks) {
+  if (weeks === 0) {
+    return date_value;
+  } else {
+    let value3 = weeks;
+    if (value3 > 0) {
+      return add_days(date_value, value3 * 7);
+    } else {
+      let value4 = weeks;
+      return subtract_date_days(date_value, 0 - value4 * 7);
+    }
+  }
+}
+function current_week_dates(week_offset) {
+  let today = now();
+  let _block;
+  let $ = day(today);
+  if ($ === 0) {
+    _block = 6;
+  } else {
+    let day2 = $;
+    _block = day2 - 1;
+  }
+  let days_from_monday = _block;
+  let current_week_start = subtract_days(year(today), month(today) + 1, date(today), days_from_monday);
+  let start5 = shift_week(current_week_start, week_offset);
+  let _pipe = toList([0, 1, 2, 3, 4, 5, 6]);
+  return map2(_pipe, (offset) => {
+    return [
+      format_date_no_year(add_days(start5, offset)),
+      format_date(add_days(start5, offset)),
+      (() => {
+        let _pipe$1 = offset;
+        return num_to_day(_pipe$1);
+      })()
+    ];
+  });
+}
+function week_label(week_offset) {
+  let dates = current_week_dates(week_offset);
+  let _block;
+  let _pipe = first(dates);
+  _block = unwrap2(_pipe, ["", "", ""]);
+  let first2 = _block;
+  let _block$1;
+  let _pipe$1 = last(dates);
+  _block$1 = unwrap2(_pipe$1, ["", "", ""]);
+  let last2 = _block$1;
+  return first2[1] + " – " + last2[1];
+}
+function calendar_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("calendar")]))
+        ])),
+        nav(List$Empty$const, toList([
+          button(toList([on_click(Message$DashboardView$const)]), toList([text3("Board view")])),
+          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$("panel")]), toList([
+        h2(List$Empty$const, toList([text3("Find tasks")])),
+        input(toList([
+          class$("task-search"),
+          placeholder("Search owner or assignees"),
+          on_input((var0) => {
+            return new TaskSearchChanged(var0);
+          }),
+          styles(toList([["width", "100%"], ["box-sizing", "border-box"]]))
+        ])),
+        select(toList([
+          class$("subteam-filter"),
+          on_change((var0) => {
+            return new SubteamFilterChanged(var0);
+          }),
+          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"]
+          ]))
+        ]), 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"),
+        styles(toList([["padding", "0"], ["overflow", "hidden"]]))
+      ]), toList([
+        div(toList([
+          styles(toList([
+            ["display", "flex"],
+            ["justify-content", "space-between"],
+            ["align-items", "center"],
+            ["padding", "14px 16px"],
+            ["border-bottom", "1px solid #e2d9cc"]
+          ]))
+        ]), toList([
+          button(toList([on_click(Message$PreviousWeek$const)]), toList([text3("← Previous week")])),
+          strong(List$Empty$const, toList([text3(week_label(model.calendar_week))])),
+          button(toList([on_click(Message$NextWeek$const)]), toList([text3("Next week →")]))
+        ])),
+        div(toList([
+          class$("calendar-grid"),
+          styles(toList([
+            ["display", "grid"],
+            ["grid-template-columns", "repeat(7, minmax(150px, 1fr))"],
+            ["overflow-x", "auto"]
+          ]))
+        ]), map2(current_week_dates(model.calendar_week), (x2) => {
+          let day_no_year = x2[0];
+          let day2 = x2[1];
+          let day_of_week = x2[2];
+          return calendar_day(day_no_year, day_of_week, calendar_tasks(board.projects, model, day2));
+        }))
+      ]))
+    ]));
+  } else {
+    return main(toList([class$("auth")]), toList([
+      styles2(),
+      h1(List$Empty$const, toList([text3("Loading Iron Claw")]))
+    ]));
+  }
+}
 function table_cell(children) {
   return td(toList([
     styles(toList([
@@ -7911,18 +8324,6 @@ function task_row(row) {
     ]))
   ]));
 }
-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([
@@ -7951,6 +8352,7 @@ function task_list_view(model) {
         ])),
         nav(List$Empty$const, toList([
           button(toList([on_click(Message$DashboardView$const)]), toList([text3("Board view")])),
+          button(toList([on_click(Message$CalendarView$const)]), toList([text3("Calendar")])),
           button(toList([on_click(Message$LogOut$const)]), toList([text3("Sign out")]))
         ]))
       ])),
@@ -8150,6 +8552,8 @@ function view(model) {
     });
   } else if ($ === "tasks") {
     return task_list_view(model);
+  } else if ($ === "calendar") {
+    return calendar_view(model);
   } else {
     return dashboard_view(model);
   }
@@ -8161,7 +8565,7 @@ function remove_project(model, project_id) {
     let projects = filter(board.projects, (project) => {
       return project.id !== project_id;
     });
-    return new Model(model.page, new Some(new Dashboard(board.username, projects)), 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);
+    return new Model(model.page, new Some(new Dashboard(board.username, projects)), 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, model.calendar_week);
   } else {
     return model;
   }
@@ -8176,7 +8580,7 @@ function remove_task(model, task_id) {
       });
       return new Project(project.id, project.name, project.description, project.archived, tasks);
     });
-    return new Model(model.page, new Some(new Dashboard(board.username, projects)), 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);
+    return new Model(model.page, new Some(new Dashboard(board.username, projects)), 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, model.calendar_week);
   } else {
     return model;
   }
@@ -8196,7 +8600,7 @@ function update_task_status(model, task_id, status) {
       });
       return new Project(project.id, project.name, project.description, project.archived, tasks);
     });
-    return new Model(model.page, new Some(new Dashboard(board.username, projects)), 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);
+    return new Model(model.page, new Some(new Dashboard(board.username, projects)), 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, model.calendar_week);
   } else {
     return model;
   }
@@ -8253,12 +8657,12 @@ function update2(model, message2) {
     if ($ instanceof Ok) {
       let value3 = $[0];
       return [
-        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),
+        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, model.calendar_week),
         none()
       ];
     } else {
       return [
-        new Model("login", model.dashboard, new Some("Please sign in to continue."), 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("login", model.dashboard, new Some("Please sign in to continue."), 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, model.calendar_week),
         none()
       ];
     }
@@ -8275,7 +8679,7 @@ function update2(model, message2) {
     _block = new_project_json(_pipe);
     let body2 = _block;
     return [
-      new Model(model.page, model.dashboard, model.error, model.username, model.password, model.invite_password, "", "", 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, model.dashboard, model.error, model.username, model.password, model.invite_password, "", "", 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, model.calendar_week),
       post("/api/projects", body2, expect_any_response((result) => {
         return new Saved((() => {
           let _pipe$1 = result;
@@ -8298,7 +8702,7 @@ function update2(model, message2) {
     _block = new_task_json(_pipe);
     let body2 = _block;
     return [
-      new Model(model.page, model.dashboard, model.error, model.username, model.password, model.invite_password, model.project_name, model.project_description, "", "", "", "", List$Empty$const, "", model.subteam_filter, model.task_search),
+      new Model(model.page, model.dashboard, model.error, model.username, model.password, model.invite_password, model.project_name, model.project_description, "", "", "", "", List$Empty$const, "", model.subteam_filter, model.task_search, model.calendar_week),
       post("/api/projects/" + to_string(project_id) + "/tasks", body2, expect_any_response((result) => {
         return new Saved((() => {
           let _pipe$1 = result;
@@ -8331,14 +8735,14 @@ function update2(model, message2) {
     let $ = message2[0];
     if ($ instanceof Ok) {
       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),
+        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, model.calendar_week),
         get3("/api/dashboard", expect_json(dashboard_decoder(), (var0) => {
           return new DashboardLoaded(var0);
         }))
       ];
     } else {
       return [
-        new Model(model.page, model.dashboard, new Some("Invalid username or password."), 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, model.dashboard, new Some("Invalid username or password."), 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, model.calendar_week),
         none()
       ];
     }
@@ -8346,94 +8750,94 @@ function update2(model, message2) {
     let $ = message2[0];
     if ($ instanceof Ok) {
       return [
-        new Model(model.page, model.dashboard, model.error, model.username, model.password, "", "", "", "", "", "", "", List$Empty$const, "", model.subteam_filter, model.task_search),
+        new Model(model.page, model.dashboard, model.error, model.username, model.password, "", "", "", "", "", "", "", List$Empty$const, "", model.subteam_filter, model.task_search, model.calendar_week),
         get3("/api/dashboard", expect_json(dashboard_decoder(), (var0) => {
           return new DashboardLoaded(var0);
         }))
       ];
     } else {
       return [
-        new Model(model.page, model.dashboard, new Some("The request could not be saved."), 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, model.dashboard, new Some("The request could not be saved."), 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, model.calendar_week),
         none()
       ];
     }
   } else if (message2 instanceof UsernameChanged) {
     let value3 = message2[0];
     return [
-      new Model(model.page, model.dashboard, model.error, value3, 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, model.dashboard, model.error, value3, 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, model.calendar_week),
       none()
     ];
   } else if (message2 instanceof PasswordChanged) {
     let value3 = message2[0];
     return [
-      new Model(model.page, model.dashboard, model.error, model.username, value3, 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, model.dashboard, model.error, model.username, value3, 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, model.calendar_week),
       none()
     ];
   } else if (message2 instanceof ProjectNameChanged) {
     let value3 = message2[0];
     return [
-      new Model(model.page, model.dashboard, model.error, model.username, model.password, model.invite_password, value3, 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, model.dashboard, model.error, model.username, model.password, model.invite_password, value3, 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, model.calendar_week),
       none()
     ];
   } else if (message2 instanceof ProjectDescriptionChanged) {
     let value3 = message2[0];
     return [
-      new Model(model.page, model.dashboard, model.error, model.username, model.password, model.invite_password, model.project_name, value3, 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, model.dashboard, model.error, model.username, model.password, model.invite_password, model.project_name, value3, 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, model.calendar_week),
       none()
     ];
   } else if (message2 instanceof TaskTitleChanged) {
     let value3 = message2[0];
     return [
-      new Model(model.page, model.dashboard, model.error, model.username, model.password, model.invite_password, model.project_name, model.project_description, value3, 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, model.dashboard, model.error, model.username, model.password, model.invite_password, model.project_name, model.project_description, value3, model.task_description, model.task_assignee, model.task_assignees, model.task_subteams, model.task_due_date, model.subteam_filter, model.task_search, model.calendar_week),
       none()
     ];
   } else if (message2 instanceof TaskDescriptionChanged) {
     let value3 = message2[0];
     return [
-      new Model(model.page, model.dashboard, model.error, model.username, model.password, model.invite_password, model.project_name, model.project_description, model.task_title, value3, model.task_assignee, model.task_assignees, model.task_subteams, model.task_due_date, model.subteam_filter, model.task_search),
+      new Model(model.page, model.dashboard, model.error, model.username, model.password, model.invite_password, model.project_name, model.project_description, model.task_title, value3, model.task_assignee, model.task_assignees, model.task_subteams, model.task_due_date, model.subteam_filter, model.task_search, model.calendar_week),
       none()
     ];
   } else if (message2 instanceof TaskAssigneeChanged) {
     let value3 = message2[0];
     return [
-      new Model(model.page, model.dashboard, model.error, model.username, model.password, model.invite_password, model.project_name, model.project_description, model.task_title, model.task_description, value3, model.task_assignees, model.task_subteams, model.task_due_date, model.subteam_filter, model.task_search),
+      new Model(model.page, model.dashboard, model.error, model.username, model.password, model.invite_password, model.project_name, model.project_description, model.task_title, model.task_description, value3, model.task_assignees, model.task_subteams, model.task_due_date, model.subteam_filter, model.task_search, model.calendar_week),
       none()
     ];
   } else if (message2 instanceof TaskAssigneesChanged) {
     let value3 = message2[0];
     return [
-      new Model(model.page, 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, value3, model.task_subteams, model.task_due_date, model.subteam_filter, model.task_search),
+      new Model(model.page, 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, value3, model.task_subteams, model.task_due_date, model.subteam_filter, model.task_search, model.calendar_week),
       none()
     ];
   } else if (message2 instanceof TaskDueDateChanged) {
     let value3 = message2[0];
     return [
-      new Model(model.page, 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, value3, model.subteam_filter, model.task_search),
+      new Model(model.page, 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, value3, model.subteam_filter, model.task_search, model.calendar_week),
       none()
     ];
   } else if (message2 instanceof TaskSubteamChanged) {
     let subteam = message2[0];
     let checked2 = message2[1];
     return [
-      new Model(model.page, 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, toggle_subteam(model.task_subteams, subteam, checked2), model.task_due_date, model.subteam_filter, model.task_search),
+      new Model(model.page, 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, toggle_subteam(model.task_subteams, subteam, checked2), model.task_due_date, model.subteam_filter, model.task_search, model.calendar_week),
       none()
     ];
   } else if (message2 instanceof SubteamFilterChanged) {
     let value3 = message2[0];
     return [
-      new Model(model.page, 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, value3, model.task_search),
+      new Model(model.page, 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, value3, model.task_search, model.calendar_week),
       none()
     ];
   } else if (message2 instanceof TaskSearchChanged) {
     let value3 = message2[0];
     return [
-      new Model(model.page, 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, value3),
+      new Model(model.page, 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, value3, model.calendar_week),
       none()
     ];
   } else if (message2 instanceof InvitePasswordChanged) {
     let value3 = message2[0];
     return [
-      new Model(model.page, model.dashboard, model.error, model.username, model.password, value3, 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, model.dashboard, model.error, model.username, model.password, value3, 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, model.calendar_week),
       none()
     ];
   } else if (message2 instanceof DeleteTask) {
@@ -8464,7 +8868,7 @@ function update2(model, message2) {
     ];
   } else if (message2 instanceof LogOut) {
     return [
-      new Model("login", 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),
+      new Model("login", 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, model.calendar_week),
       get3("/api/logout", expect_ok_response((_) => {
         return Message$Nothing$const;
       }))
@@ -8499,12 +8903,27 @@ 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),
+      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, model.calendar_week),
+      none()
+    ];
+  } else if (message2 instanceof DashboardView) {
+    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, model.calendar_week),
+      none()
+    ];
+  } else if (message2 instanceof CalendarView) {
+    return [
+      new Model("calendar", 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, model.calendar_week),
+      none()
+    ];
+  } else if (message2 instanceof PreviousWeek) {
+    return [
+      new Model(model.page, 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, model.calendar_week - 1),
       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),
+      new Model(model.page, 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, model.calendar_week + 1),
       none()
     ];
   }
@@ -8523,11 +8942,13 @@ function init(_) {
     _block = "register";
   } else if ($ === "/tasks") {
     _block = "tasks";
+  } else if ($ === "/calendar") {
+    _block = "calendar";
   } else {
     _block = "dashboard";
   }
   let page = _block;
-  let model = new Model(page, Option$None$const, Option$None$const, "", "", "", "", "", "", "", "", "", List$Empty$const, "", "all", "");
+  let model = new Model(page, Option$None$const, Option$None$const, "", "", "", "", "", "", "", "", "", List$Empty$const, "", "all", "", 0);
   if (page === "dashboard") {
     return [
       model,
@@ -8542,6 +8963,13 @@ function init(_) {
         return new DashboardLoaded(var0);
       }))
     ];
+  } else if (page === "calendar") {
+    return [
+      model,
+      get3("/api/dashboard", expect_json(dashboard_decoder(), (var0) => {
+        return new DashboardLoaded(var0);
+      }))
+    ];
   } else if (page === "login") {
     return [
       model,
@@ -8557,7 +8985,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", 23, "main", "Pattern match failed, no pattern matched the value.", { value: $, start: 527, end: 577, pattern_start: 538, pattern_end: 543 });
+    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 });
   }
   return;
 }