feat: ability to complete a task #35
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -2907,6 +2907,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"time",
|
||||
"tracing",
|
||||
"tracing-wasm",
|
||||
"validator",
|
||||
|
@ -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 = []
|
||||
|
@ -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<Task>, class: Option<&'static str>) -> Element {
|
||||
let query_client = use_query_client::<QueryValue, QueryErrors, QueryKey>();
|
||||
let mut task_being_edited = use_context::<Signal<Option<Task>>>();
|
||||
|
||||
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()}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ impl FromSql<Jsonb, Pg> for Category {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Hash, Clone, Debug)]
|
||||
#[derive(Serialize, Deserialize, PartialEq, Hash, Clone, Debug)]
|
||||
pub enum ReoccurrenceInterval {
|
||||
![]() Approved: Enhancements to The addition of Also applies to: 105-107 **Approved: Enhancements to `ReoccurrenceInterval` and `Reoccurrence`.**
The addition of `PartialEq` to `ReoccurrenceInterval` and the new `start_date` method in `Reoccurrence` are beneficial for improving the model's functionality and encapsulation. Consider adding documentation for the new `start_date` method to clarify its usage and purpose.
Also applies to: 105-107
<!-- This is an auto-generated comment by CodeRabbit -->
|
||||
Day,
|
||||
Month,
|
||||
@ -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
|
||||
}
|
||||
|
@ -69,3 +69,9 @@ impl NewTask {
|
||||
Self { title, deadline, category, project_id }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Task> for NewTask {
|
||||
fn from(task: Task) -> Self {
|
||||
Self::new(task.title, task.deadline, task.category, task.project_id)
|
||||
}
|
||||
}
|
||||
![]() Approved: Implementation of The new implementation for converting a Would you like me to help with writing the unit tests for this conversion? **Approved: Implementation of `From<Task> for NewTask`.**
The new implementation for converting a `Task` instance to a `NewTask` is well-implemented and promotes code reuse and simplicity. Consider adding unit tests to cover this new functionality to ensure it behaves as expected.
Would you like me to help with writing the unit tests for this conversion?
<!-- This is an auto-generated comment by CodeRabbit -->
|
||||
|
@ -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<Task, ServerFnError<ErrorVec<Error>>> {
|
||||
use crate::schema::tasks::dsl::*;
|
||||
|
||||
let mut connection = establish_database_connection()
|
||||
.map_err::<ErrorVec<Error>, _>(|_| vec![Error::ServerInternal].into())?;
|
||||
|
||||
let task = tasks
|
||||
.find(task_id)
|
||||
.select(Task::as_select())
|
||||
.first(&mut connection)
|
||||
.optional()
|
||||
.map_err::<ErrorVec<Error>, _>(|_| 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<Vec<Task>, ServerFnError<ErrorVec<Error>>> {
|
||||
@ -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<Task, ServerFnError<ErrorVec<Error>>> {
|
||||
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::<ErrorVec<Error>, _>(|_| vec![Error::ServerInternal].into())?;
|
||||
|
||||
Ok(updated_task)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user
Refactoring of task rendering and event handling logic.
The refactoring of the task rendering logic using
map
andclone
improves readability and immutability. The conditional rendering for task properties and CSS class determination is well-implemented and concise.The asynchronous
onclick
event handler for task completion is a significant enhancement. It correctly usesasync move
to handle the task completion without blocking the UI, followed by invalidating queries to update the UI state. This is a robust implementation that leverages modern Rust asynchronous programming practices.Consider adding error handling for the
complete_task
function within theasync move
block to manage potential failures gracefully.