feat: ability to complete a task (#35)

This commit is contained in:
Matouš Volf 2024-09-07 14:58:36 +02:00 committed by GitHub
commit ac97deb996
6 changed files with 142 additions and 44 deletions

1
Cargo.lock generated
View File

@ -2907,6 +2907,7 @@ dependencies = [
"serde",
"serde_json",
"serde_with",
"time",
"tracing",
"tracing-wasm",
"validator",

View File

@ -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 = []

View File

@ -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()}
}
}
}
}
}
}
}
}
})}
}
}
}

View File

@ -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 {
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
}

View File

@ -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)
}
}

View File

@ -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)
}