From c873d6b378afef1aef03cdb22a369a7fa25b49a2 Mon Sep 17 00:00:00 2001 From: Taran Nathan Date: Thu, 24 Sep 2026 16:54:44 -0700 Subject: [PATCH] speed up with state modifications before psql response --- client/src/client.gleam | 130 +++++++++++++++++++++++++++---- server/static/client.js | 164 +++++++++++++++++++++++++++++++++++----- 2 files changed, 262 insertions(+), 32 deletions(-) diff --git a/client/src/client.gleam b/client/src/client.gleam index c08fdf3..a47dc99 100644 --- a/client/src/client.gleam +++ b/client/src/client.gleam @@ -159,7 +159,7 @@ fn update(model: Model, message: Message) -> #(Model, Effect(Message)) { shared.NewProject(field(fields, "name"), field(fields, "description")) |> shared.new_project_json #( - model, + Model(..model, project_name: "", project_description: ""), rsvp.post( "/api/projects", body, @@ -180,7 +180,14 @@ fn update(model: Model, message: Message) -> #(Model, Effect(Message)) { ) |> shared.new_task_json #( - model, + Model( + ..model, + task_title: "", + task_description: "", + task_assignee: "", + task_assignees: "", + task_due_date: "", + ), rsvp.post( "/api/projects/" <> int.to_string(project_id) <> "/tasks", body, @@ -194,7 +201,7 @@ fn update(model: Model, message: Message) -> #(Model, Effect(Message)) { let status = field(fields, "status") let body = shared.StatusChange(status) |> shared.status_change_json #( - model, + update_task_status(model, task_id, status), rsvp.post( "/api/tasks/" <> int.to_string(task_id) <> "/status", body, @@ -205,7 +212,7 @@ fn update(model: Model, message: Message) -> #(Model, Effect(Message)) { ) } DeleteTask(task_id) -> #( - model, + remove_task(model, task_id), rsvp.delete( "/api/tasks/" <> int.to_string(task_id), json.object([]), @@ -215,7 +222,7 @@ fn update(model: Model, message: Message) -> #(Model, Effect(Message)) { ), ) DeleteProject(project_id) -> #( - model, + remove_project(model, project_id), rsvp.delete( "/api/projects/" <> int.to_string(project_id), json.object([]), @@ -264,6 +271,61 @@ fn update(model: Model, message: Message) -> #(Model, Effect(Message)) { } } +fn update_task_status(model: Model, task_id: Int, status: String) -> Model { + case model.dashboard { + None -> model + Some(board) -> { + let projects = + list.map(board.projects, fn(project) { + let tasks = + list.map(project.tasks, fn(task) { + case task.id == task_id { + True -> shared.Task(..task, status: status) + False -> task + } + }) + shared.Project(..project, tasks: tasks) + }) + Model( + ..model, + dashboard: Some(shared.Dashboard(..board, projects: projects)), + ) + } + } +} + +fn remove_task(model: Model, task_id: Int) -> Model { + case model.dashboard { + None -> model + Some(board) -> { + let projects = + list.map(board.projects, fn(project) { + let tasks = + list.filter(project.tasks, fn(task) { task.id != task_id }) + shared.Project(..project, tasks: tasks) + }) + Model( + ..model, + dashboard: Some(shared.Dashboard(..board, projects: projects)), + ) + } + } +} + +fn remove_project(model: Model, project_id: Int) -> Model { + case model.dashboard { + None -> model + Some(board) -> { + let projects = + list.filter(board.projects, fn(project) { project.id != project_id }) + Model( + ..model, + dashboard: Some(shared.Dashboard(..board, projects: projects)), + ) + } + } +} + fn auth_request( model: Model, fields: List(#(String, String)), @@ -291,11 +353,11 @@ fn auth_request( fn view(model: Model) -> Element(Message) { case model.page { - "login" -> auth_view(model, "Welcome back", "Log in", LoginSubmitted) + "login" -> auth_view(model, "Greetings Comrade", "Log in", LoginSubmitted) "register" -> creat_account_view( model, - "Create your account", + "Create account", "Create account", RegisterSubmitted, ) @@ -359,7 +421,11 @@ fn creat_account_view( styles(), html.text("FRC 972 · IRON CLAW"), html.h1([], [html.text(title)]), - html.p([], [html.text("google sheets, more like shmoogle shits")]), + html.p([], [ + html.text( + "google sheets, more like shmoogle shits (also passwords need to be at least 8 chars and username needs to be at least 3)", + ), + ]), error_view(model.error), html.form([event.on_submit(on_submit)], [ html.input([ @@ -389,7 +455,7 @@ fn dashboard_view(model: Model) -> Element(Message) { case model.dashboard { None -> html.main([attribute.class("auth")], [ - html.h1([], [html.text("Loading Iron Claw...")]), + html.h1([], [html.text("Loading Iron Claw")]), error_view(model.error), ]) Some(board) -> @@ -450,14 +516,44 @@ fn project_view(_model: Model, project: shared.Project) -> Element(Message) { ], [ html.form( - [event.on_submit(fn(fields) { TaskSubmitted(project.id, fields) })], + [ + attribute.class("task-form"), + attribute.styles([ + #("display", "grid"), + #("grid-template-columns", "repeat(4, minmax(0, 1fr))"), + #("align-items", "start"), + ]), + event.on_submit(fn(fields) { TaskSubmitted(project.id, fields) }), + ], [ text_input("title", "Task title"), text_input("assignee", "Owner"), text_input("assignees", "Assignees"), text_input("due_date", "Due (MM/DD/YY)"), - text_input("description", "Details"), - html.button([], [html.text("Add task")]), + html.textarea( + [ + attribute.class("task-description-input"), + attribute.styles([ + #("grid-column", "1 / -1"), + #("width", "100%"), + #("box-sizing", "border-box"), + #("min-height", "96px"), + #("resize", "vertical"), + #("padding", "12px 13px"), + #("border", "1px solid #d9cfc2"), + #("background", "#fffcf8"), + #("color", "#28231e"), + #("font", "inherit"), + ]), + attribute.name("description"), + attribute.placeholder("Details"), + attribute.rows(4), + ], + "", + ), + html.button([attribute.class("task-submit")], [ + html.text("Add task"), + ]), ], ), ], @@ -491,7 +587,13 @@ fn task_view(task: shared.Task) -> Element(Message) { ], ), ]), - html.p([attribute.class("task-description")], [html.text(task.description)]), + html.p( + [ + attribute.class("task-description"), + attribute.style("white-space", "pre-wrap"), + ], + [html.text(task.description)], + ), html.div([attribute.class("task-details")], [ html.div([attribute.class("task-person")], [ html.small([], [html.text("Owner")]), @@ -566,7 +668,7 @@ fn text_input(name: String, placeholder: String) -> Element(Message) { fn styles() -> Element(Message) { html.style( [], - "body{margin:0;background:#f4f0e8;color:#28231e;font-family:Inter,ui-sans-serif,system-ui,sans-serif}.page{max-width:1120px;margin:auto;padding:28px 22px 72px}.auth{max-width:430px;margin:10vh auto;padding:42px;background:#fff;border:1px solid #e2d9cc;box-shadow:0 20px 60px #2d241414}.auth h1{font-size:clamp(2.4rem,8vw,4.8rem);line-height:.95;letter-spacing:-.06em;margin:14px 0}.auth form{display:grid;gap:12px;margin:26px 0 14px}.auth a{color:#b53b24;font-weight:700}.eyebrow{font-size:.72rem;letter-spacing:.16em;color:#b53b24;font-weight:800}header{display:flex;justify-content:space-between;gap:24px;align-items:start;margin-bottom:42px}header h1{font-size:clamp(2.7rem,7vw,5.5rem);line-height:.9;letter-spacing:-.07em;margin:12px 0}.hero{padding:42px;background:linear-gradient(120deg,#29241f,#594538);color:#fff;margin-bottom:20px;font-size:clamp(1.5rem,3vw,2.5rem);font-weight:800;letter-spacing:-.04em}.panel{background:#fff;border:1px solid #e2d9cc;padding:24px;margin:18px 0;box-shadow:0 12px 30px #2d24140b}.panel h2{margin:5px 0 8px;letter-spacing:-.03em}.panel>p{color:#796f64}.kanban{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin-top:22px}.kanban-column{background:#f7f3ed;border:1px solid #ebe1d4;padding:13px}.kanban-column h3{font-size:.8rem;letter-spacing:.08em;text-transform:uppercase;color:#796f64;margin:0 0 5px}.task{display:flex;flex-direction:column;gap:13px;background:#fff;border:1px solid #e8dfd4;padding:16px;margin:9px 0;box-shadow:0 4px 12px #2d24140a;min-width:0}.task-header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.task-title{font-size:1rem;line-height:1.25;overflow-wrap:anywhere}.delete-task{flex:0 0 auto;padding:5px 8px;font-size:.68rem;background:#f0ebe3;color:#796f64}.delete-task:hover{background:#e8dfd4;color:#28231e}.task-description{margin:0;color:#796f64;line-height:1.45;overflow-wrap:anywhere}.task-details{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:11px 14px;padding-top:12px;border-top:1px solid #eee6dc;color:#4e463e;font-size:.8rem}.task-person,.task-due{display:flex;flex-direction:column;gap:4px;min-width:0;overflow-wrap:anywhere}.task-details small{color:#a04a35;font-size:.68rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase}.task-due{grid-column:1/-1}.task-footer{display:flex;align-items:center;justify-content:space-between;gap:10px;padding-top:2px}.task-status{padding:5px 8px;background:#f0ebe3;color:#796f64;font-size:.68rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase}.task-actions{display:flex;gap:6px;margin-left:auto}.task-actions form{margin:0}.move-task{width:31px;height:29px;padding:0;font-size:1.05rem;line-height:1}.move-task:hover{background:#902e1d}form{display:flex;gap:10px;flex-wrap:wrap;margin-top:22px}input{min-width:150px;flex:1;padding:12px 13px;border:1px solid #d9cfc2;background:#fffcf8;color:#28231e;font:inherit}button{border:0;background:#b53b24;color:#fff;padding:12px 17px;font:inherit;font-weight:800;cursor:pointer}button:hover{background:#902e1d}.error{color:#b53b24;font-weight:700}a{color:inherit;text-decoration:none}@media(max-width:800px){.kanban{grid-template-columns:1fr 1fr}}@media(max-width:650px){.kanban{grid-template-columns:1fr}.task-details{grid-template-columns:1fr}.task-due{grid-column:auto}header{display:block}.hero{padding:28px}}", + "body{margin:0;background:#f4f0e8;color:#28231e;font-family:Inter,ui-sans-serif,system-ui,sans-serif}.page{max-width:1120px;margin:auto;padding:28px 22px 72px}.auth{max-width:430px;margin:10vh auto;padding:42px;background:#fff;border:1px solid #e2d9cc;box-shadow:0 20px 60px #2d241414}.auth h1{font-size:clamp(2.4rem,8vw,4.8rem);line-height:.95;letter-spacing:-.06em;margin:14px 0}.auth form{display:grid;gap:12px;margin:26px 0 14px}.auth a{color:rgb(255, 100, 0);font-weight:700}.eyebrow{font-size:.72rem;letter-spacing:.16em;color:rgb(255, 100, 0);font-weight:800}header{display:flex;justify-content:space-between;gap:24px;align-items:start;margin-bottom:42px}header h1{font-size:clamp(2.7rem,7vw,5.5rem);line-height:.9;letter-spacing:-.07em;margin:12px 0}.hero{padding:42px;background:linear-gradient(120deg,#29241f,#594538);color:#fff;margin-bottom:20px;font-size:clamp(1.5rem,3vw,2.5rem);font-weight:800;letter-spacing:-.04em}.panel{background:#fff;border:1px solid #e2d9cc;padding:24px;margin:18px 0;box-shadow:0 12px 30px #2d24140b}.panel h2{margin:5px 0 8px;letter-spacing:-.03em}.panel>p{color:#796f64}.kanban{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin-top:22px}.kanban-column{background:#f7f3ed;border:1px solid #ebe1d4;padding:13px}.kanban-column h3{font-size:.8rem;letter-spacing:.08em;text-transform:uppercase;color:#796f64;margin:0 0 5px}.task{display:flex;flex-direction:column;gap:13px;background:#fff;border:1px solid #e8dfd4;padding:16px;margin:9px 0;box-shadow:0 4px 12px #2d24140a;min-width:0}.task-header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.task-title{font-size:1rem;line-height:1.25;overflow-wrap:anywhere}.delete-task{flex:0 0 auto;padding:5px 8px;font-size:.68rem;background:#f0ebe3;color:#796f64}.delete-task:hover{background:#e8dfd4;color:#28231e}.task-description{margin:0;color:#796f64;line-height:1.45;overflow-wrap:anywhere}.task-details{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:11px 14px;padding-top:12px;border-top:1px solid #eee6dc;color:#4e463e;font-size:.8rem}.task-person,.task-due{display:flex;flex-direction:column;gap:4px;min-width:0;overflow-wrap:anywhere}.task-details small{color:#000000;font-size:.68rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase}.task-due{grid-column:1/-1}.task-footer{display:flex;align-items:center;justify-content:space-between;gap:10px;padding-top:2px}.task-status{padding:5px 8px;background:#f0ebe3;color:#796f64;font-size:.68rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase}.task-actions{display:flex;gap:6px;margin-left:auto}.task-actions form{margin:0}.move-task{width:31px;height:29px;padding:0;font-size:1.05rem;line-height:1}.move-task:hover{background:#902e1d}form{display:flex;gap:10px;flex-wrap:wrap;margin-top:22px}input{min-width:150px;flex:1;padding:12px 13px;border:1px solid #d9cfc2;background:#fffcf8;color:#28231e;font:inherit}button{border:0;background:rgb(255, 100, 0);color:#fff;padding:12px 17px;font:inherit;font-weight:800;cursor:pointer}button:hover{background:#902e1d}.error{color:rgb(255, 100, 0);font-weight:700}a{color:inherit;text-decoration:none}@media(max-width:800px){.kanban{grid-template-columns:1fr 1fr}}@media(max-width:650px){.kanban{grid-template-columns:1fr}.task-details{grid-template-columns:1fr}.task-due{grid-column:auto}header{display:block}.hero{padding:28px}}", ) } diff --git a/server/static/client.js b/server/static/client.js index 1e44740..cfce598 100644 --- a/server/static/client.js +++ b/server/static/client.js @@ -2239,6 +2239,9 @@ var always = /* @__PURE__ */ new Always(always_kind); function attribute(name, value) { return new Attribute(attribute_kind, name, value); } +function property(name, value) { + return new Property(property_kind, name, value); +} function event(name, handler, include, prevent_default, stop_propagation, debounce, throttle) { return new Event2(event_kind, name, handler, include, prevent_default, stop_propagation, debounce, throttle); } @@ -2372,9 +2375,53 @@ function prepare(attributes) { function attribute2(name, value) { return attribute(name, value); } +function property2(name, value) { + return property(name, value); +} function class$(name) { return attribute2("class", name); } +function style(property3, value) { + if (property3 === "") { + return class$(""); + } else if (value === "") { + return class$(""); + } else { + return attribute2("style", property3 + ":" + value + ";"); + } +} +function do_styles(loop$properties, loop$styles) { + while (true) { + let properties = loop$properties; + let styles = loop$styles; + if (properties instanceof Empty) { + return styles; + } else { + let $ = properties.head[0]; + if ($ === "") { + let rest = properties.tail; + loop$properties = rest; + loop$styles = styles; + } else { + let $1 = properties.head[1]; + if ($1 === "") { + let rest = properties.tail; + loop$properties = rest; + loop$styles = styles; + } else { + let rest = properties.tail; + let name$1 = $; + let value$1 = $1; + loop$properties = rest; + loop$styles = styles + name$1 + ":" + value$1 + ";"; + } + } + } + } +} +function styles(properties) { + return attribute2("style", do_styles(properties, "")); +} function href(url) { return attribute2("href", url); } @@ -2384,6 +2431,9 @@ function name(element_name) { function placeholder(text) { return attribute2("placeholder", text); } +function rows(value) { + return attribute2("rows", to_string(value)); +} function type_(control_type) { return attribute2("type", control_type); } @@ -2688,7 +2738,7 @@ function map6(element3, f) { function text3(content) { return text2(content); } -function style(attrs, css) { +function style2(attrs, css) { return unsafe_raw_html("", "style", attrs, css); } function article(attrs, children) { @@ -2739,6 +2789,9 @@ function form(attrs, children) { function input(attrs) { return element2("input", attrs, empty_list); } +function textarea(attrs, content) { + return element2("textarea", prepend(property2("value", string4(content)), attrs), toList([text2(content)])); +} // build/dev/javascript/lustre/lustre/vdom/patch.mjs class Patch extends CustomType { @@ -7232,7 +7285,10 @@ function task_view(task) { class$("delete-task") ]), toList([text3("Delete")])) ])), - p(toList([class$("task-description")]), toList([text3(task.description)])), + p(toList([ + class$("task-description"), + style("white-space", "pre-wrap") + ]), toList([text3(task.description)])), div(toList([class$("task-details")]), toList([ div(toList([class$("task-person")]), toList([ small(List$Empty$const, toList([text3("Owner")])), @@ -7290,6 +7346,12 @@ function project_view(_, project) { ])) ]), toList([ form(toList([ + class$("task-form"), + styles(toList([ + ["display", "grid"], + ["grid-template-columns", "repeat(4, minmax(0, 1fr))"], + ["align-items", "start"] + ])), on_submit((fields) => { return new TaskSubmitted(project.id, fields); }) @@ -7298,13 +7360,30 @@ function project_view(_, project) { text_input("assignee", "Owner"), text_input("assignees", "Assignees"), text_input("due_date", "Due (MM/DD/YY)"), - text_input("description", "Details"), - button(List$Empty$const, toList([text3("Add task")])) + textarea(toList([ + class$("task-description-input"), + styles(toList([ + ["grid-column", "1 / -1"], + ["width", "100%"], + ["box-sizing", "border-box"], + ["min-height", "96px"], + ["resize", "vertical"], + ["padding", "12px 13px"], + ["border", "1px solid #d9cfc2"], + ["background", "#fffcf8"], + ["color", "#28231e"], + ["font", "inherit"] + ])), + name("description"), + placeholder("Details"), + rows(4) + ]), ""), + button(toList([class$("task-submit")]), toList([text3("Add task")])) ])) ])))); } -function styles() { - return style(List$Empty$const, "body{margin:0;background:#f4f0e8;color:#28231e;font-family:Inter,ui-sans-serif,system-ui,sans-serif}.page{max-width:1120px;margin:auto;padding:28px 22px 72px}.auth{max-width:430px;margin:10vh auto;padding:42px;background:#fff;border:1px solid #e2d9cc;box-shadow:0 20px 60px #2d241414}.auth h1{font-size:clamp(2.4rem,8vw,4.8rem);line-height:.95;letter-spacing:-.06em;margin:14px 0}.auth form{display:grid;gap:12px;margin:26px 0 14px}.auth a{color:#b53b24;font-weight:700}.eyebrow{font-size:.72rem;letter-spacing:.16em;color:#b53b24;font-weight:800}header{display:flex;justify-content:space-between;gap:24px;align-items:start;margin-bottom:42px}header h1{font-size:clamp(2.7rem,7vw,5.5rem);line-height:.9;letter-spacing:-.07em;margin:12px 0}.hero{padding:42px;background:linear-gradient(120deg,#29241f,#594538);color:#fff;margin-bottom:20px;font-size:clamp(1.5rem,3vw,2.5rem);font-weight:800;letter-spacing:-.04em}.panel{background:#fff;border:1px solid #e2d9cc;padding:24px;margin:18px 0;box-shadow:0 12px 30px #2d24140b}.panel h2{margin:5px 0 8px;letter-spacing:-.03em}.panel>p{color:#796f64}.kanban{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin-top:22px}.kanban-column{background:#f7f3ed;border:1px solid #ebe1d4;padding:13px}.kanban-column h3{font-size:.8rem;letter-spacing:.08em;text-transform:uppercase;color:#796f64;margin:0 0 5px}.task{display:flex;flex-direction:column;gap:13px;background:#fff;border:1px solid #e8dfd4;padding:16px;margin:9px 0;box-shadow:0 4px 12px #2d24140a;min-width:0}.task-header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.task-title{font-size:1rem;line-height:1.25;overflow-wrap:anywhere}.delete-task{flex:0 0 auto;padding:5px 8px;font-size:.68rem;background:#f0ebe3;color:#796f64}.delete-task:hover{background:#e8dfd4;color:#28231e}.task-description{margin:0;color:#796f64;line-height:1.45;overflow-wrap:anywhere}.task-details{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:11px 14px;padding-top:12px;border-top:1px solid #eee6dc;color:#4e463e;font-size:.8rem}.task-person,.task-due{display:flex;flex-direction:column;gap:4px;min-width:0;overflow-wrap:anywhere}.task-details small{color:#a04a35;font-size:.68rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase}.task-due{grid-column:1/-1}.task-footer{display:flex;align-items:center;justify-content:space-between;gap:10px;padding-top:2px}.task-status{padding:5px 8px;background:#f0ebe3;color:#796f64;font-size:.68rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase}.task-actions{display:flex;gap:6px;margin-left:auto}.task-actions form{margin:0}.move-task{width:31px;height:29px;padding:0;font-size:1.05rem;line-height:1}.move-task:hover{background:#902e1d}form{display:flex;gap:10px;flex-wrap:wrap;margin-top:22px}input{min-width:150px;flex:1;padding:12px 13px;border:1px solid #d9cfc2;background:#fffcf8;color:#28231e;font:inherit}button{border:0;background:#b53b24;color:#fff;padding:12px 17px;font:inherit;font-weight:800;cursor:pointer}button:hover{background:#902e1d}.error{color:#b53b24;font-weight:700}a{color:inherit;text-decoration:none}@media(max-width:800px){.kanban{grid-template-columns:1fr 1fr}}@media(max-width:650px){.kanban{grid-template-columns:1fr}.task-details{grid-template-columns:1fr}.task-due{grid-column:auto}header{display:block}.hero{padding:28px}}"); +function styles2() { + return style2(List$Empty$const, "body{margin:0;background:#f4f0e8;color:#28231e;font-family:Inter,ui-sans-serif,system-ui,sans-serif}.page{max-width:1120px;margin:auto;padding:28px 22px 72px}.auth{max-width:430px;margin:10vh auto;padding:42px;background:#fff;border:1px solid #e2d9cc;box-shadow:0 20px 60px #2d241414}.auth h1{font-size:clamp(2.4rem,8vw,4.8rem);line-height:.95;letter-spacing:-.06em;margin:14px 0}.auth form{display:grid;gap:12px;margin:26px 0 14px}.auth a{color:rgb(255, 100, 0);font-weight:700}.eyebrow{font-size:.72rem;letter-spacing:.16em;color:rgb(255, 100, 0);font-weight:800}header{display:flex;justify-content:space-between;gap:24px;align-items:start;margin-bottom:42px}header h1{font-size:clamp(2.7rem,7vw,5.5rem);line-height:.9;letter-spacing:-.07em;margin:12px 0}.hero{padding:42px;background:linear-gradient(120deg,#29241f,#594538);color:#fff;margin-bottom:20px;font-size:clamp(1.5rem,3vw,2.5rem);font-weight:800;letter-spacing:-.04em}.panel{background:#fff;border:1px solid #e2d9cc;padding:24px;margin:18px 0;box-shadow:0 12px 30px #2d24140b}.panel h2{margin:5px 0 8px;letter-spacing:-.03em}.panel>p{color:#796f64}.kanban{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin-top:22px}.kanban-column{background:#f7f3ed;border:1px solid #ebe1d4;padding:13px}.kanban-column h3{font-size:.8rem;letter-spacing:.08em;text-transform:uppercase;color:#796f64;margin:0 0 5px}.task{display:flex;flex-direction:column;gap:13px;background:#fff;border:1px solid #e8dfd4;padding:16px;margin:9px 0;box-shadow:0 4px 12px #2d24140a;min-width:0}.task-header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.task-title{font-size:1rem;line-height:1.25;overflow-wrap:anywhere}.delete-task{flex:0 0 auto;padding:5px 8px;font-size:.68rem;background:#f0ebe3;color:#796f64}.delete-task:hover{background:#e8dfd4;color:#28231e}.task-description{margin:0;color:#796f64;line-height:1.45;overflow-wrap:anywhere}.task-details{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:11px 14px;padding-top:12px;border-top:1px solid #eee6dc;color:#4e463e;font-size:.8rem}.task-person,.task-due{display:flex;flex-direction:column;gap:4px;min-width:0;overflow-wrap:anywhere}.task-details small{color:#000000;font-size:.68rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase}.task-due{grid-column:1/-1}.task-footer{display:flex;align-items:center;justify-content:space-between;gap:10px;padding-top:2px}.task-status{padding:5px 8px;background:#f0ebe3;color:#796f64;font-size:.68rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase}.task-actions{display:flex;gap:6px;margin-left:auto}.task-actions form{margin:0}.move-task{width:31px;height:29px;padding:0;font-size:1.05rem;line-height:1}.move-task:hover{background:#902e1d}form{display:flex;gap:10px;flex-wrap:wrap;margin-top:22px}input{min-width:150px;flex:1;padding:12px 13px;border:1px solid #d9cfc2;background:#fffcf8;color:#28231e;font:inherit}button{border:0;background:rgb(255, 100, 0);color:#fff;padding:12px 17px;font:inherit;font-weight:800;cursor:pointer}button:hover{background:#902e1d}.error{color:rgb(255, 100, 0);font-weight:700}a{color:inherit;text-decoration:none}@media(max-width:800px){.kanban{grid-template-columns:1fr 1fr}}@media(max-width:650px){.kanban{grid-template-columns:1fr}.task-details{grid-template-columns:1fr}.task-due{grid-column:auto}header{display:block}.hero{padding:28px}}"); } function error_view(error) { if (error instanceof Some) { @@ -7319,7 +7398,7 @@ function dashboard_view(model) { if ($ instanceof Some) { let board = $[0]; return main(toList([class$("page")]), append(toList([ - styles(), + styles2(), header(List$Empty$const, toList([ div(List$Empty$const, toList([ text3("FRC 972 · IRON CLAW"), @@ -7345,7 +7424,7 @@ function dashboard_view(model) { }))); } else { return main(toList([class$("auth")]), toList([ - h1(List$Empty$const, toList([text3("Loading Iron Claw...")])), + h1(List$Empty$const, toList([text3("Loading Iron Claw")])), error_view(model.error) ])); } @@ -7368,10 +7447,12 @@ function creat_account_view(model, title2, submit, on_submit2) { } let link_text = _block$1; return main(toList([class$("auth")]), toList([ - styles(), + styles2(), text3("FRC 972 · IRON CLAW"), h1(List$Empty$const, toList([text3(title2)])), - p(List$Empty$const, toList([text3("google sheets, more like shmoogle shits")])), + p(List$Empty$const, toList([ + text3("google sheets, more like shmoogle shits (also passwords need to be at least 8 chars and username needs to be at least 3)") + ])), error_view(model.error), form(toList([on_submit(on_submit2)]), toList([ input(toList([ @@ -7420,7 +7501,7 @@ function auth_view(model, title2, submit, on_submit2) { } let link_text = _block$1; return main(toList([class$("auth")]), toList([ - styles(), + styles2(), text3("Team 972 · IRON CLAW"), h1(List$Empty$const, toList([text3(title2)])), p(List$Empty$const, toList([text3("google sheets, more like shmoogle shits")])), @@ -7449,17 +7530,64 @@ function auth_view(model, title2, submit, on_submit2) { function view(model) { let $ = model.page; if ($ === "login") { - return auth_view(model, "Welcome back", "Log in", (var0) => { + return auth_view(model, "Greetings Comrade", "Log in", (var0) => { return new LoginSubmitted(var0); }); } else if ($ === "register") { - return creat_account_view(model, "Create your account", "Create account", (var0) => { + return creat_account_view(model, "Create account", "Create account", (var0) => { return new RegisterSubmitted(var0); }); } else { return dashboard_view(model); } } +function remove_project(model, project_id) { + let $ = model.dashboard; + if ($ instanceof Some) { + let board = $[0]; + 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_due_date); + } else { + return model; + } +} +function remove_task(model, task_id) { + let $ = model.dashboard; + if ($ instanceof Some) { + let board = $[0]; + let projects = map2(board.projects, (project) => { + let tasks = filter(project.tasks, (task) => { + return task.id !== task_id; + }); + return new Project(project.id, project.name, project.description, 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_due_date); + } else { + return model; + } +} +function update_task_status(model, task_id, status) { + let $ = model.dashboard; + if ($ instanceof Some) { + let board = $[0]; + let projects = map2(board.projects, (project) => { + let tasks = map2(project.tasks, (task) => { + let $1 = task.id === task_id; + if ($1) { + return new Task(task.id, task.project_id, task.title, task.description, status, task.assignee, task.assignees, task.due_date); + } else { + return task; + } + }); + return new Project(project.id, project.name, project.description, 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_due_date); + } else { + return model; + } +} function field2(fields, name2) { let _pipe = key_find(fields, name2); return unwrap2(_pipe, ""); @@ -7512,7 +7640,7 @@ function update2(model, message2) { _block = new_project_json(_pipe); let body2 = _block; return [ - model, + 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_due_date), post("/api/projects", body2, expect_any_response((result) => { return new Saved((() => { let _pipe$1 = result; @@ -7530,7 +7658,7 @@ function update2(model, message2) { _block = new_task_json(_pipe); let body2 = _block; return [ - model, + new Model(model.page, model.dashboard, model.error, model.username, model.password, model.invite_password, model.project_name, model.project_description, "", "", "", "", ""), post("/api/projects/" + to_string(project_id) + "/tasks", body2, expect_any_response((result) => { return new Saved((() => { let _pipe$1 = result; @@ -7549,7 +7677,7 @@ function update2(model, message2) { _block = status_change_json(_pipe); let body2 = _block; return [ - model, + update_task_status(model, task_id, status), post("/api/tasks/" + to_string(task_id) + "/status", body2, expect_any_response((result) => { return new Saved((() => { let _pipe$1 = result; @@ -7652,7 +7780,7 @@ function update2(model, message2) { } else if (message2 instanceof DeleteTask) { let task_id = message2[0]; return [ - model, + remove_task(model, task_id), delete$2("/api/tasks/" + to_string(task_id), object2(List$Empty$const), expect_any_response((result) => { return new Saved((() => { let _pipe = result; @@ -7665,7 +7793,7 @@ function update2(model, message2) { } else if (message2 instanceof DeleteProject) { let project_id = message2[0]; return [ - model, + remove_project(model, project_id), delete$2("/api/projects/" + to_string(project_id), object2(List$Empty$const), expect_any_response((result) => { return new Saved((() => { let _pipe = result; -- 2.47.3