feat: add the ability to edit a task upon clicking in a list
This commit is contained in:
		| @@ -1,9 +1,10 @@ | ||||
| use dioxus::prelude::*; | ||||
| use crate::components::navigation::Navigation; | ||||
| use crate::components::project_form::ProjectForm; | ||||
| use crate::components::task_form::TaskForm; | ||||
| use crate::models::project::Project; | ||||
| use crate::models::task::Task; | ||||
| use crate::route::Route; | ||||
| use dioxus::prelude::*; | ||||
|  | ||||
| #[component] | ||||
| pub(crate) fn BottomPanel(display_form: Signal<bool>) -> Element { | ||||
| @@ -12,8 +13,9 @@ pub(crate) fn BottomPanel(display_form: Signal<bool>) -> Element { | ||||
|     let mut expanded = use_signal(|| display_form()); | ||||
|     let navigation_expanded = use_signal(|| false); | ||||
|     let current_route = use_route(); | ||||
|      | ||||
|  | ||||
|     let mut project_being_edited = use_context::<Signal<Option<Project>>>(); | ||||
|     let mut task_being_edited = use_context::<Signal<Option<Task>>>(); | ||||
|  | ||||
|     use_effect(use_reactive(&display_form, move |display_form| { | ||||
|         if display_form() { | ||||
| @@ -22,7 +24,11 @@ pub(crate) fn BottomPanel(display_form: Signal<bool>) -> Element { | ||||
|             spawn(async move { | ||||
|                 // Necessary for a smooth – not instant – height transition. | ||||
|                 async_std::task::sleep(std::time::Duration::from_millis(500)).await; | ||||
|                 expanded.set(false); | ||||
|                 /* The check is necessary for the situation when the user expands the panel while | ||||
|                    it is being closed. */ | ||||
|                 if !display_form() { | ||||
|                     expanded.set(false); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|     })); | ||||
| @@ -51,8 +57,10 @@ pub(crate) fn BottomPanel(display_form: Signal<bool>) -> Element { | ||||
|                     }, | ||||
|                     _ => rsx! { | ||||
|                         TaskForm { | ||||
|                             task: task_being_edited(), | ||||
|                             on_successful_submit: move |_| { | ||||
|                                 display_form.set(false); | ||||
|                                 task_being_edited.set(None); | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|   | ||||
| @@ -74,7 +74,7 @@ pub(crate) fn CategoryInput(selected_category: Signal<Category>, class: Option<& | ||||
|                 ), | ||||
|                 onclick: move |_| { | ||||
|                     selected_category.set(Category::Calendar { | ||||
|                         date: NaiveDate::default(), | ||||
|                         date: chrono::Local::now().date_naive(), | ||||
|                         reoccurrence: None, | ||||
|                         time: None, | ||||
|                     }); | ||||
|   | ||||
| @@ -1,16 +1,19 @@ | ||||
| use dioxus::prelude::*; | ||||
| use crate::models::project::Project; | ||||
| use crate::models::task::Task; | ||||
| use dioxus::prelude::*; | ||||
|  | ||||
| #[component] | ||||
| pub(crate) fn FormOpenButton(opened: Signal<bool>) -> Element { | ||||
|     let mut project_being_edited = use_context::<Signal<Option<Project>>>(); | ||||
|      | ||||
|     let mut task_being_edited = use_context::<Signal<Option<Task>>>(); | ||||
|  | ||||
|     rsx! { | ||||
|         button { | ||||
|             class: "pointer-events-auto m-4 py-3 px-5 self-end text-center bg-zinc-300/50 rounded-xl border-t-zinc-200 border-t backdrop-blur drop-shadow-[0_-5px_10px_rgba(0,0,0,0.2)] text-2xl text-zinc-200", | ||||
|             onclick: move |_| { | ||||
|                 if opened() { | ||||
|                     project_being_edited.set(None); | ||||
|                     task_being_edited.set(None); | ||||
|                 } | ||||
|                 opened.set(!opened()); | ||||
|             }, | ||||
|   | ||||
| @@ -1,11 +1,12 @@ | ||||
| use crate::components::bottom_panel::BottomPanel; | ||||
| use crate::components::form_open_button::FormOpenButton; | ||||
| use crate::components::sticky_bottom::StickyBottom; | ||||
| use crate::models::project::Project; | ||||
| use crate::models::task::Task; | ||||
| use crate::route::Route; | ||||
| use dioxus::core_macro::rsx; | ||||
| use dioxus::dioxus_core::Element; | ||||
| use dioxus::prelude::*; | ||||
| use crate::components::form_open_button::FormOpenButton; | ||||
| use crate::components::sticky_bottom::StickyBottom; | ||||
| use crate::models::project::Project; | ||||
|  | ||||
| #[component] | ||||
| pub(crate) fn Layout() -> Element { | ||||
| @@ -13,9 +14,12 @@ pub(crate) fn Layout() -> Element { | ||||
|     let project_being_edited = use_context_provider::<Signal<Option<Project>>>( | ||||
|         || Signal::new(None) | ||||
|     ); | ||||
|     let task_being_edited = use_context_provider::<Signal<Option<Task>>>( | ||||
|         || Signal::new(None) | ||||
|     ); | ||||
|      | ||||
|     use_effect(move || { | ||||
|         display_form.set(project_being_edited().is_some()); | ||||
|         display_form.set(project_being_edited().is_some() || task_being_edited().is_some()); | ||||
|     }); | ||||
|      | ||||
|     rsx! { | ||||
|   | ||||
| @@ -2,15 +2,16 @@ use crate::components::category_input::CategoryInput; | ||||
| use crate::components::reoccurrence_input::ReoccurrenceIntervalInput; | ||||
| use crate::models::category::{CalendarTime, Category, Reoccurrence}; | ||||
| use crate::models::task::NewTask; | ||||
| use crate::models::task::Task; | ||||
| use crate::query::{QueryErrors, QueryKey, QueryValue}; | ||||
| use crate::route::Route; | ||||
| use crate::server::projects::get_projects; | ||||
| use crate::server::tasks::create_task; | ||||
| use chrono::{Duration, NaiveDate}; | ||||
| use crate::server::tasks::{create_task, edit_task}; | ||||
| use chrono::{Duration}; | ||||
| use dioxus::core_macro::{component, rsx}; | ||||
| use dioxus::dioxus_core::Element; | ||||
| use dioxus::prelude::*; | ||||
| use dioxus_query::prelude::use_query_client; | ||||
| use crate::query::{QueryErrors, QueryKey, QueryValue}; | ||||
| use crate::route::Route; | ||||
|  | ||||
| const REMINDER_OFFSETS: [Option<Duration>; 17] = [ | ||||
|     None, | ||||
| @@ -33,31 +34,54 @@ const REMINDER_OFFSETS: [Option<Duration>; 17] = [ | ||||
| ]; | ||||
|  | ||||
| #[component] | ||||
| pub(crate) fn TaskForm(on_successful_submit: EventHandler<()>) -> Element { | ||||
| pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()>) -> Element { | ||||
|     let projects = use_server_future(get_projects)?.unwrap().unwrap(); | ||||
|  | ||||
|     let route = use_route::<Route>(); | ||||
|     let selected_category = use_signal(|| match route {  | ||||
|         Route::CategorySomedayMaybePage => Category::SomedayMaybe, | ||||
|         Route::CategoryWaitingForPage => Category::WaitingFor(String::new()), | ||||
|         Route::CategoryNextStepsPage => Category::NextSteps, | ||||
|         Route::CategoryCalendarPage | Route::CategoryTodayPage => Category::Calendar { | ||||
|             date: NaiveDate::default(), | ||||
|             reoccurrence: None, | ||||
|             time: None, | ||||
|         }, | ||||
|         Route::CategoryLongTermPage => Category::LongTerm, | ||||
|         _ => Category::Inbox, | ||||
|     }); | ||||
|     let category_calendar_reoccurrence_interval = use_signal(|| None); | ||||
|     let mut category_calendar_has_time = use_signal(|| false); | ||||
|     let mut category_calendar_reminder_offset_index = use_signal(|| REMINDER_OFFSETS.len() - 1); | ||||
|     let selected_category = use_signal(|| if let Some(task) = &task { | ||||
|         task.category().clone() | ||||
|     } else { | ||||
|         match route { | ||||
|             Route::CategorySomedayMaybePage => Category::SomedayMaybe, | ||||
|             Route::CategoryWaitingForPage => Category::WaitingFor(String::new()), | ||||
|             Route::CategoryNextStepsPage => Category::NextSteps, | ||||
|             Route::CategoryCalendarPage | Route::CategoryTodayPage => Category::Calendar { | ||||
|                 date: chrono::Local::now().date_naive(), | ||||
|                 reoccurrence: None, | ||||
|                 time: None, | ||||
|             }, | ||||
|             Route::CategoryLongTermPage => Category::LongTerm, | ||||
|             _ => Category::Inbox, | ||||
|         } | ||||
|     } | ||||
|     ); | ||||
|     let category_calendar_reoccurrence_interval = use_signal(|| task.as_ref().and_then(|task| | ||||
|         if let Category::Calendar { reoccurrence: Some(reoccurrence), .. } = task.category() { | ||||
|             Some(reoccurrence.interval().clone()) | ||||
|         } else { | ||||
|             None | ||||
|         } | ||||
|     )); | ||||
|     let mut category_calendar_has_time = use_signal(|| task.as_ref().is_some_and( | ||||
|         |task| matches!(*task.category(), Category::Calendar { time: Some(_), .. })) | ||||
|     ); | ||||
|     let mut category_calendar_reminder_offset_index = use_signal(|| task.as_ref().and_then(|task| | ||||
|         if let Category::Calendar { time: Some(time), .. } = task.category() { | ||||
|             REMINDER_OFFSETS.iter().position(|&reminder_offset| | ||||
|                 reminder_offset == time.reminder_offset() | ||||
|             ) | ||||
|         } else { | ||||
|             None | ||||
|         } | ||||
|     ).unwrap_or(REMINDER_OFFSETS.len() - 1)); | ||||
|  | ||||
|     let query_client = use_query_client::<QueryValue, QueryErrors, QueryKey>(); | ||||
|      | ||||
|     let task_for_submit = task.clone(); | ||||
|  | ||||
|     rsx! { | ||||
|         form { | ||||
|             onsubmit: move |event| { | ||||
|                 let task = task_for_submit.clone(); | ||||
|                 async move { | ||||
|                     let new_task = NewTask::new( | ||||
|                         event.values().get("title").unwrap().as_value(), | ||||
| @@ -96,7 +120,11 @@ pub(crate) fn TaskForm(on_successful_submit: EventHandler<()>) -> Element { | ||||
|                         event.values().get("project_id").unwrap() | ||||
|                         .as_value().parse::<i32>().ok().filter(|&id| id > 0), | ||||
|                     ); | ||||
|                     let _ = create_task(new_task).await; | ||||
|                     if let Some(task) = task { | ||||
|                         let _ = edit_task(task.id(), new_task).await; | ||||
|                     } else { | ||||
|                         let _ = create_task(new_task).await; | ||||
|                     } | ||||
|                     query_client.invalidate_queries(&[ | ||||
|                         QueryKey::Tasks,  | ||||
|                         QueryKey::TasksInCategory(selected_category()) | ||||
| @@ -115,9 +143,10 @@ pub(crate) fn TaskForm(on_successful_submit: EventHandler<()>) -> Element { | ||||
|                     }, | ||||
|                 }, | ||||
|                 input { | ||||
|                     r#type: "text", | ||||
|                     name: "title", | ||||
|                     required: true, | ||||
|                     initial_value: task.as_ref().map(|task| task.title().to_owned()), | ||||
|                     r#type: "text", | ||||
|                     class: "py-2 px-3 grow bg-zinc-800/50 rounded-lg", | ||||
|                     id: "input_title" | ||||
|                 }, | ||||
| @@ -142,6 +171,9 @@ pub(crate) fn TaskForm(on_successful_submit: EventHandler<()>) -> Element { | ||||
|                     for project in projects { | ||||
|                         option { | ||||
|                             value: project.id().to_string(), | ||||
|                             selected: task.as_ref().is_some_and( | ||||
|                                 |task| task.project_id() == Some(project.id()) | ||||
|                             ), | ||||
|                             {project.title()} | ||||
|                         } | ||||
|                     } | ||||
| @@ -157,8 +189,10 @@ pub(crate) fn TaskForm(on_successful_submit: EventHandler<()>) -> Element { | ||||
|                     } | ||||
|                 }, | ||||
|                 input { | ||||
|                     r#type: "date", | ||||
|                     name: "deadline", | ||||
|                     initial_value: task.as_ref().and_then(|task| task.deadline()) | ||||
|                         .map(|deadline| deadline.format("%Y-%m-%d").to_string()), | ||||
|                     r#type: "date", | ||||
|                     class: "py-2 px-3 bg-zinc-800/50 rounded-lg grow basis-0", | ||||
|                     id: "input_deadline" | ||||
|                 } | ||||
| @@ -177,7 +211,7 @@ pub(crate) fn TaskForm(on_successful_submit: EventHandler<()>) -> Element { | ||||
|                 } | ||||
|             } | ||||
|             match selected_category() { | ||||
|                 Category::WaitingFor(_) => rsx! { | ||||
|                 Category::WaitingFor(waiting_for) => rsx! { | ||||
|                     div { | ||||
|                         class: "flex flex-row items-center gap-3", | ||||
|                         label { | ||||
| @@ -188,15 +222,16 @@ pub(crate) fn TaskForm(on_successful_submit: EventHandler<()>) -> Element { | ||||
|                             } | ||||
|                         }, | ||||
|                         input { | ||||
|                             r#type: "text", | ||||
|                             name: "category_waiting_for", | ||||
|                             required: true, | ||||
|                             initial_value: waiting_for, | ||||
|                             r#type: "text", | ||||
|                             class: "py-2 px-3 bg-zinc-800/50 rounded-lg grow", | ||||
|                             id: "input_category_waiting_for" | ||||
|                         }, | ||||
|                     } | ||||
|                 }, | ||||
|                 Category::Calendar { .. } => rsx! { | ||||
|                 Category::Calendar { date, reoccurrence, time } => rsx! { | ||||
|                     div { | ||||
|                         class: "flex flex-row items-center gap-3", | ||||
|                         label { | ||||
| @@ -212,13 +247,16 @@ pub(crate) fn TaskForm(on_successful_submit: EventHandler<()>) -> Element { | ||||
|                                 r#type: "date", | ||||
|                                 name: "category_calendar_date", | ||||
|                                 required: true, | ||||
|                                 initial_value: chrono::Local::now().format("%Y-%m-%d").to_string(), | ||||
|                                 initial_value: date.format("%Y-%m-%d").to_string(), | ||||
|                                 class: "py-2 px-3 bg-zinc-800/50 rounded-lg grow", | ||||
|                                 id: "input_category_calendar_date" | ||||
|                             }, | ||||
|                             input { | ||||
|                                 r#type: "time", | ||||
|                                 name: "category_calendar_time", | ||||
|                                 initial_value: time.map(|calendar_time| | ||||
|                                     calendar_time.time().format("%H:%M").to_string() | ||||
|                                 ), | ||||
|                                 class: "py-2 px-3 bg-zinc-800/50 rounded-lg grow", | ||||
|                                 id: "input_category_calendar_time", | ||||
|                                 oninput: move |event| { | ||||
| @@ -248,9 +286,9 @@ pub(crate) fn TaskForm(on_successful_submit: EventHandler<()>) -> Element { | ||||
|                                 disabled: category_calendar_reoccurrence_interval().is_none(), | ||||
|                                 required: true, | ||||
|                                 min: 1, | ||||
|                                 initial_value: | ||||
|                                 if category_calendar_reoccurrence_interval().is_none() { "" } | ||||
|                                 else { "1" }, | ||||
|                                 initial_value: category_calendar_reoccurrence_interval() | ||||
|                                     .map_or(String::new(), |_| reoccurrence.map_or(1, |reoccurrence| | ||||
|                                         reoccurrence.length()).to_string()), | ||||
|                                 class: "py-2 px-3 bg-zinc-800/50 rounded-lg col-span-2 text-right", | ||||
|                                 id: "category_calendar_reoccurrence_length" | ||||
|                             } | ||||
| @@ -271,7 +309,8 @@ pub(crate) fn TaskForm(on_successful_submit: EventHandler<()>) -> Element { | ||||
|                                 name: "category_calendar_reminder_offset_index", | ||||
|                                 min: 0, | ||||
|                                 max: REMINDER_OFFSETS.len() as i64 - 1, | ||||
|                                 initial_value: REMINDER_OFFSETS.len() as i64 - 1, | ||||
|                                 initial_value: category_calendar_reminder_offset_index() | ||||
|                                     .to_string(), | ||||
|                                 class: "grow input-range-reverse", | ||||
|                                 id: "category_calendar_has_reminder", | ||||
|                                 oninput: move |event| { | ||||
|   | ||||
| @@ -6,6 +6,8 @@ use dioxus::prelude::*; | ||||
|  | ||||
| #[component] | ||||
| pub(crate) fn TaskList(tasks: Vec<Task>, class: Option<&'static str>) -> Element { | ||||
|     let mut task_being_edited = use_context::<Signal<Option<Task>>>(); | ||||
|  | ||||
|     rsx! { | ||||
|         div { | ||||
|             class: format!("flex flex-col {}", class.unwrap_or("")), | ||||
| @@ -13,7 +15,7 @@ pub(crate) fn TaskList(tasks: Vec<Task>, class: Option<&'static str>) -> Element | ||||
|                 div { | ||||
|                     key: "{task.id()}", | ||||
|                     class: format!( | ||||
|                         "px-8 pt-5 {} flex flex-row gap-4", | ||||
|                         "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() { | ||||
| @@ -24,8 +26,12 @@ pub(crate) fn TaskList(tasks: Vec<Task>, class: Option<&'static str>) -> Element | ||||
|                             } | ||||
|                         } 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", | ||||
|                     }, | ||||
|   | ||||
| @@ -6,13 +6,13 @@ use std::str::FromStr; | ||||
| use validator::{ValidationErrors, ValidationErrorsKind}; | ||||
| 
 | ||||
| #[derive(Serialize, Deserialize, Debug)] | ||||
| pub enum TaskCreateError { | ||||
| pub enum TaskError { | ||||
|     TitleLengthInvalid, | ||||
|     ProjectNotFound, | ||||
|     Error(Error), | ||||
| } | ||||
| 
 | ||||
| impl From<ValidationErrors> for ErrorVec<TaskCreateError> { | ||||
| impl From<ValidationErrors> for ErrorVec<TaskError> { | ||||
|     fn from(validation_errors: ValidationErrors) -> Self { | ||||
|         validation_errors.errors() | ||||
|             .iter() | ||||
| @@ -22,31 +22,49 @@ impl From<ValidationErrors> for ErrorVec<TaskCreateError> { | ||||
|                         .iter() | ||||
|                         .map(|validation_error| validation_error.code.as_ref()) | ||||
|                         .map(|code| match code { | ||||
|                             "title_length" => TaskCreateError::TitleLengthInvalid, | ||||
|                             "title_length" => TaskError::TitleLengthInvalid, | ||||
|                             _ => panic!("Unexpected validation error code: `{code}`."), | ||||
|                         }) | ||||
|                         .collect::<Vec<TaskCreateError>>(), | ||||
|                         .collect::<Vec<TaskError>>(), | ||||
|                     _ => panic!("Unexpected validation error kind."), | ||||
|                 }, | ||||
|                 _ => panic!("Unexpected validation field name: `{field}`."), | ||||
|             }) | ||||
|             .collect::<Vec<TaskCreateError>>() | ||||
|             .collect::<Vec<TaskError>>() | ||||
|             .into() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl From<diesel::result::Error> for TaskError { | ||||
|     fn from(diesel_error: diesel::result::Error) -> Self { | ||||
|         match diesel_error { | ||||
|             diesel::result::Error::DatabaseError( | ||||
|                 diesel::result::DatabaseErrorKind::ForeignKeyViolation, info | ||||
|             ) => { | ||||
|                 match info.constraint_name() { | ||||
|                     Some("tasks_project_id_fkey") => TaskError::ProjectNotFound, | ||||
|                     _ => TaskError::Error(Error::ServerInternal) | ||||
|                 } | ||||
|             } | ||||
|             _ => { | ||||
|                 TaskError::Error(Error::ServerInternal) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // Has to be implemented for Dioxus server functions.
 | ||||
| impl Display for TaskCreateError { | ||||
| impl Display for TaskError { | ||||
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||
|         write!(f, "{:?}", self) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // Has to be implemented for Dioxus server functions.
 | ||||
| impl FromStr for TaskCreateError { | ||||
| impl FromStr for TaskError { | ||||
|     type Err = (); | ||||
| 
 | ||||
|     fn from_str(_: &str) -> Result<Self, Self::Err> { | ||||
|         Ok(TaskCreateError::Error(Error::ServerInternal)) | ||||
|         Ok(TaskError::Error(Error::ServerInternal)) | ||||
|     } | ||||
| } | ||||
| @@ -2,52 +2,37 @@ 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::{QueryDsl, RunQueryDsl, SelectableHelper}; | ||||
| use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl, SelectableHelper}; | ||||
| use dioxus::prelude::*; | ||||
| use validator::Validate; | ||||
| use crate::errors::task_create_error::TaskCreateError; | ||||
| use crate::errors::task_error::TaskError; | ||||
| use crate::models::category::Category; | ||||
|  | ||||
| #[server] | ||||
| pub(crate) async fn create_task(new_task: NewTask) | ||||
|                                 -> Result<Task, ServerFnError<ErrorVec<TaskCreateError>>> { | ||||
|                                 -> Result<Task, ServerFnError<ErrorVec<TaskError>>> { | ||||
|     use crate::schema::tasks; | ||||
|  | ||||
|     new_task.validate() | ||||
|         .map_err::<ErrorVec<TaskCreateError>, _>(|errors| errors.into())?; | ||||
|         .map_err::<ErrorVec<TaskError>, _>(|errors| errors.into())?; | ||||
|  | ||||
|     let mut connection = establish_database_connection() | ||||
|         .map_err::<ErrorVec<TaskCreateError>, _>( | ||||
|             |_| vec![TaskCreateError::Error(Error::ServerInternal)].into() | ||||
|         .map_err::<ErrorVec<TaskError>, _>( | ||||
|             |_| vec![TaskError::Error(Error::ServerInternal)].into() | ||||
|         )?; | ||||
|  | ||||
|     let new_task = diesel::insert_into(tasks::table) | ||||
|         .values(&new_task) | ||||
|         .returning(Task::as_returning()) | ||||
|         .get_result(&mut connection) | ||||
|         .map_err::<ErrorVec<TaskCreateError>, _>(|error| { | ||||
|             let error = match error { | ||||
|                 diesel::result::Error::DatabaseError( | ||||
|                     diesel::result::DatabaseErrorKind::ForeignKeyViolation, info | ||||
|                 ) => { | ||||
|                     match info.constraint_name() {  | ||||
|                         Some("tasks_project_id_fkey") => TaskCreateError::ProjectNotFound, | ||||
|                         _ => TaskCreateError::Error(Error::ServerInternal) | ||||
|                     } | ||||
|                 }, | ||||
|                 _ => { | ||||
|                     TaskCreateError::Error(Error::ServerInternal) | ||||
|                 } | ||||
|             }; | ||||
|             vec![error].into() | ||||
|         })?; | ||||
|         .map_err::<ErrorVec<TaskError>, _>(|error| vec![error.into()].into())?; | ||||
|  | ||||
|     Ok(new_task) | ||||
| } | ||||
|  | ||||
| #[server] | ||||
| pub(crate) async fn get_tasks_in_category(filtered_category: Category) | ||||
|     -> Result<Vec<Task>, ServerFnError<ErrorVec<Error>>> { | ||||
|                                           -> Result<Vec<Task>, ServerFnError<ErrorVec<Error>>> { | ||||
|     use crate::schema::tasks::dsl::*; | ||||
|  | ||||
|     let mut connection = establish_database_connection() | ||||
| @@ -65,3 +50,31 @@ pub(crate) async fn get_tasks_in_category(filtered_category: Category) | ||||
|  | ||||
|     Ok(results) | ||||
| } | ||||
|  | ||||
| #[server] | ||||
| pub(crate) async fn edit_task(task_id: i32, new_task: NewTask) | ||||
|                               -> Result<Task, ServerFnError<ErrorVec<TaskError>>> { | ||||
|     use crate::schema::tasks::dsl::*; | ||||
|  | ||||
|     new_task.validate() | ||||
|         .map_err::<ErrorVec<TaskError>, _>(|errors| errors.into())?; | ||||
|  | ||||
|     let mut connection = establish_database_connection() | ||||
|         .map_err::<ErrorVec<TaskError>, _>( | ||||
|             |_| vec![TaskError::Error(Error::ServerInternal)].into() | ||||
|         )?; | ||||
|  | ||||
|     let updated_task = diesel::update(tasks) | ||||
|         .filter(id.eq(task_id)) | ||||
|         .set(( | ||||
|             title.eq(new_task.title), | ||||
|             deadline.eq(new_task.deadline), | ||||
|             category.eq(new_task.category), | ||||
|             project_id.eq(new_task.project_id), | ||||
|         )) | ||||
|         .returning(Task::as_returning()) | ||||
|         .get_result(&mut connection) | ||||
|         .map_err::<ErrorVec<TaskError>, _>(|error| vec![error.into()].into())?; | ||||
|  | ||||
|     Ok(updated_task) | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user