From 68562cbf5cdb10af88d1045eb574c4cd26564b98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Volf?= <66163112+matous-volf@users.noreply.github.com> Date: Sat, 7 Sep 2024 14:43:22 +0200 Subject: [PATCH 1/6] build: add time to dependencies --- Cargo.lock | 1 + Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 3e9311c..e8632dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2907,6 +2907,7 @@ dependencies = [ "serde", "serde_json", "serde_with", + "time", "tracing", "tracing-wasm", "validator", diff --git a/Cargo.toml b/Cargo.toml index dee2047..3fb07f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ tracing-wasm = "0.2.1" serde_with = { version = "3.9.0", features = ["chrono_0_4"] } async-std = "1.12.0" dioxus-query = "0.5.1" +time = "0.3.36" [features] default = [] -- 2.47.1 From cfb45a504d89278393c0a28ffbd87554033fddc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Volf?= <66163112+matous-volf@users.noreply.github.com> Date: Sat, 7 Sep 2024 14:48:22 +0200 Subject: [PATCH 2/6] feat: derive `PartialEq` for the reoccurrence interval model --- src/models/category.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/category.rs b/src/models/category.rs index f5a7838..4a7e0ef 100644 --- a/src/models/category.rs +++ b/src/models/category.rs @@ -83,7 +83,7 @@ impl FromSql for Category { } } -#[derive(Serialize, Deserialize, Hash, Clone, Debug)] +#[derive(Serialize, Deserialize, PartialEq, Hash, Clone, Debug)] pub enum ReoccurrenceInterval { Day, Month, -- 2.47.1 From 6e1c7bd8c8cb9029235dc35adfb645637a816fca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Volf?= <66163112+matous-volf@users.noreply.github.com> Date: Sat, 7 Sep 2024 14:48:44 +0200 Subject: [PATCH 3/6] feat: create a getter for the reoccurrence start date field --- src/models/category.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/models/category.rs b/src/models/category.rs index 4a7e0ef..91d987b 100644 --- a/src/models/category.rs +++ b/src/models/category.rs @@ -102,6 +102,10 @@ impl Reoccurrence { Self { start_date, interval, length } } + pub fn start_date(&self) -> NaiveDate { + self.start_date + } + pub fn interval(&self) -> &ReoccurrenceInterval { &self.interval } -- 2.47.1 From 594d6c675b79dd7baf3db26aca17fca9ed663c1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Volf?= <66163112+matous-volf@users.noreply.github.com> Date: Sat, 7 Sep 2024 14:49:38 +0200 Subject: [PATCH 4/6] feat: implement `From` for `NewTask` --- src/models/task.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/models/task.rs b/src/models/task.rs index 4aeb34d..caa1d7f 100644 --- a/src/models/task.rs +++ b/src/models/task.rs @@ -69,3 +69,9 @@ impl NewTask { Self { title, deadline, category, project_id } } } + +impl From for NewTask { + fn from(task: Task) -> Self { + Self::new(task.title, task.deadline, task.category, task.project_id) + } +} -- 2.47.1 From c7990a9adfeb7a27b516651af89b5ff2cc5f65d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Volf?= <66163112+matous-volf@users.noreply.github.com> Date: Sat, 7 Sep 2024 14:49:59 +0200 Subject: [PATCH 5/6] feat: add a server function for completing a task --- src/server/tasks.rs | 62 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/src/server/tasks.rs b/src/server/tasks.rs index 81efcca..91c4083 100644 --- a/src/server/tasks.rs +++ b/src/server/tasks.rs @@ -1,12 +1,14 @@ +use chrono::{Datelike, Days, Months, NaiveDate}; use crate::errors::error::Error; use crate::errors::error_vec::ErrorVec; use crate::models::task::{NewTask, Task}; use crate::server::database_connection::establish_database_connection; -use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl, SelectableHelper}; +use diesel::{ExpressionMethods, OptionalExtension, QueryDsl, RunQueryDsl, SelectableHelper}; use dioxus::prelude::*; +use time::util::days_in_year_month; use validator::Validate; use crate::errors::task_error::TaskError; -use crate::models::category::Category; +use crate::models::category::{Category, ReoccurrenceInterval}; #[server] pub(crate) async fn create_task(new_task: NewTask) @@ -30,6 +32,24 @@ pub(crate) async fn create_task(new_task: NewTask) Ok(new_task) } +#[server] +pub(crate) async fn get_task(task_id: i32) -> Result>> { + use crate::schema::tasks::dsl::*; + + let mut connection = establish_database_connection() + .map_err::, _>(|_| vec![Error::ServerInternal].into())?; + + let task = tasks + .find(task_id) + .select(Task::as_select()) + .first(&mut connection) + .optional() + .map_err::, _>(|_| vec![Error::ServerInternal].into())?; + + // TODO: Handle not finding the task. + Ok(task.unwrap()) +} + #[server] pub(crate) async fn get_tasks_in_category(filtered_category: Category) -> Result, ServerFnError>> { @@ -78,3 +98,41 @@ pub(crate) async fn edit_task(task_id: i32, new_task: NewTask) Ok(updated_task) } + +#[server] +pub(crate) async fn complete_task(task_id: i32) -> Result>> { + let task = get_task(task_id).await?; + let mut new_task = NewTask::from(task); + + if let Category::Calendar { + reoccurrence: Some(reoccurrence), + date, + .. + } = &mut new_task.category { + match reoccurrence.interval() { + ReoccurrenceInterval::Day => *date = *date + Days::new(reoccurrence.length() as u64), + ReoccurrenceInterval::Month | ReoccurrenceInterval::Year => { + *date = *date + Months::new( + reoccurrence.length() * + if *(reoccurrence.interval()) == ReoccurrenceInterval::Year + { 12 } else { 1 } + ); + *date = NaiveDate::from_ymd_opt( + date.year(), + date.month(), + reoccurrence.start_date().day().min(days_in_year_month( + date.year(), + (date.month() as u8).try_into().unwrap(), + ) as u32), + ).unwrap() + } + } + } else { + new_task.category = Category::Done; + } + + let updated_task = edit_task(task_id, new_task).await + .map_err::, _>(|_| vec![Error::ServerInternal].into())?; + + Ok(updated_task) +} -- 2.47.1 From a12d558839636f37e950be1bb3b1e2f44c52a6ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Volf?= <66163112+matous-volf@users.noreply.github.com> Date: Sat, 7 Sep 2024 14:50:14 +0200 Subject: [PATCH 6/6] feat: add the ability to complete a task --- src/components/task_list.rs | 110 ++++++++++++++++++++++-------------- 1 file changed, 69 insertions(+), 41 deletions(-) diff --git a/src/components/task_list.rs b/src/components/task_list.rs index 8429f00..a6e1959 100644 --- a/src/components/task_list.rs +++ b/src/components/task_list.rs @@ -3,70 +3,98 @@ use crate::models::task::Task; use dioxus::core_macro::rsx; use dioxus::dioxus_core::Element; use dioxus::prelude::*; +use dioxus_query::prelude::use_query_client; +use crate::query::{QueryErrors, QueryKey, QueryValue}; +use crate::server::tasks::complete_task; #[component] pub(crate) fn TaskList(tasks: Vec, class: Option<&'static str>) -> Element { + let query_client = use_query_client::(); let mut task_being_edited = use_context::>>(); rsx! { div { class: format!("flex flex-col {}", class.unwrap_or("")), - for task in tasks { - div { - key: "{task.id()}", - class: format!( - "px-8 pt-5 {} flex flex-row gap-4 select-none {}", - if task.deadline().is_some() { - "pb-0.5" - } else if let Category::Calendar { time, .. } = task.category() { - if time.is_some() { + {tasks.iter().cloned().map(|task| { + let task_clone = task.clone(); + rsx! { + div { + key: "{task.id()}", + class: format!( + "px-8 pt-5 {} flex flex-row gap-4 select-none {}", + if task.deadline().is_some() { "pb-0.5" + } else if let Category::Calendar { time, .. } = task.category() { + if time.is_some() { + "pb-0.5" + } else { + "pb-5" + } } else { "pb-5" - } - } else { - "pb-5" - }, - if task_being_edited().is_some_and(|t| t.id() == task.id()) { - "bg-zinc-700" - } else { "" } - ), - onclick: move |_| task_being_edited.set(Some(task.clone())), - i { - class: "fa-regular fa-square text-3xl text-zinc-600", - }, - div { - class: "flex flex-col", - div { - class: "mt-1 grow font-medium", - {task.title()} - }, - div { - class: "flex flex-row gap-3", - if let Some(deadline) = task.deadline() { - div { - class: "text-sm text-zinc-400", - i { - class: "fa-solid fa-bomb" - }, - {deadline.format(" %m. %d.").to_string()} + }, + if task_being_edited().is_some_and(|t| t.id() == task.id()) { + "bg-zinc-700" + } else { "" } + ), + onclick: move |_| task_being_edited.set(Some(task.clone())), + i { + class: format!( + "{} text-3xl text-zinc-500", + if *(task_clone.category()) == Category::Done { + "fa solid fa-square-check" + } else { + "fa-regular fa-square" + } + ), + onclick: move |event| { + // To prevent editing the task. + event.stop_propagation(); + let task = task_clone.clone(); + async move { + let completed_task = complete_task(task.id()).await; + query_client.invalidate_queries(&[ + QueryKey::Tasks, + QueryKey::TasksInCategory( + completed_task.unwrap().category().clone() + ), + ]); } } - if let Category::Calendar { time, .. } = task.category() { - if let Some(calendar_time) = time { + }, + div { + class: "flex flex-col", + div { + class: "mt-1 grow font-medium", + {task.title()} + }, + div { + class: "flex flex-row gap-3", + if let Some(deadline) = task.deadline() { div { class: "text-sm text-zinc-400", i { - class: "fa-solid fa-clock" + class: "fa-solid fa-bomb" }, - {calendar_time.time().format(" %k:%M").to_string()} + {deadline.format(" %m. %d.").to_string()} + } + } + if let Category::Calendar { time, .. } = task.category() { + if let Some(calendar_time) = time { + div { + class: "text-sm text-zinc-400", + i { + class: "fa-solid fa-clock" + }, + {calendar_time.time().format(" %k:%M").to_string()} + } } } } } } } - } + })} } } } -- 2.47.1