feat: list sorting #42
@ -41,7 +41,7 @@ pub(crate) fn BottomPanel(display_form: Signal<bool>) -> Element {
|
||||
(false, _, false) => "h-[66px]",
|
||||
(false, _, true) => "h-[130px]",
|
||||
(true, Route::ProjectsPage, _) => "h-[130px]",
|
||||
(true, _, _) => "h-[448px]",
|
||||
(true, _, _) => "h-[506px]",
|
||||
}
|
||||
),
|
||||
if expanded() {
|
||||
|
@ -13,3 +13,4 @@ pub(crate) mod reoccurrence_input;
|
||||
pub(crate) mod layout;
|
||||
pub(crate) mod navigation_item;
|
||||
pub(crate) mod subtasks_form;
|
||||
pub(crate) mod task_list_item;
|
||||
|
@ -6,11 +6,12 @@ use crate::query::QueryValue;
|
||||
use chrono::{Local, Locale};
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_query::prelude::QueryResult;
|
||||
use crate::components::task_list_item::TaskListItem;
|
||||
|
||||
#[component]
|
||||
pub(crate) fn CategoryTodayPage() -> Element {
|
||||
let today_date = Local::now().date_naive();
|
||||
|
||||
|
||||
let calendar_tasks_query = use_tasks_with_subtasks_in_category_query(Category::Calendar {
|
||||
date: today_date,
|
||||
reoccurrence: None,
|
||||
@ -26,48 +27,36 @@ pub(crate) fn CategoryTodayPage() -> Element {
|
||||
class: "pt-4 flex flex-col gap-8",
|
||||
match long_term_tasks_query_result.value() {
|
||||
QueryResult::Ok(QueryValue::TasksWithSubtasks(tasks))
|
||||
| QueryResult::Loading(Some(QueryValue::TasksWithSubtasks(tasks))) => rsx! {
|
||||
div {
|
||||
class: "flex flex-col gap-4",
|
||||
| QueryResult::Loading(Some(QueryValue::TasksWithSubtasks(tasks))) => {
|
||||
let mut tasks = tasks.clone();
|
||||
tasks.sort();
|
||||
rsx! {
|
||||
div {
|
||||
class: "px-8 flex flex-row items-center gap-2 font-bold",
|
||||
i {
|
||||
class: "fa-solid fa-water text-xl w-6 text-center"
|
||||
class: "flex flex-col gap-4",
|
||||
div {
|
||||
class: "px-8 flex flex-row items-center gap-2 font-bold",
|
||||
i {
|
||||
class: "fa-solid fa-water text-xl w-6 text-center"
|
||||
}
|
||||
div {
|
||||
class: "mt-1",
|
||||
"Long-term"
|
||||
}
|
||||
}
|
||||
div {
|
||||
class: "mt-1",
|
||||
"Long-term"
|
||||
}
|
||||
}
|
||||
div {
|
||||
for task in tasks {
|
||||
div {
|
||||
key: "{task.task().id()}",
|
||||
class: format!(
|
||||
"px-8 pt-5 {} flex flex-row gap-4",
|
||||
if task.task().deadline().is_some() {
|
||||
"pb-0.5"
|
||||
} else {
|
||||
"pb-5"
|
||||
}
|
||||
),
|
||||
for task in tasks {
|
||||
div {
|
||||
class: "flex flex-col",
|
||||
div {
|
||||
class: "mt grow font-medium",
|
||||
{task.task().title()}
|
||||
},
|
||||
div {
|
||||
class: "flex flex-row gap-3",
|
||||
if let Some(deadline) = task.task().deadline() {
|
||||
div {
|
||||
class: "text-sm text-zinc-400",
|
||||
i {
|
||||
class: "fa-solid fa-bomb"
|
||||
},
|
||||
{deadline.format(" %m. %d.").to_string()}
|
||||
}
|
||||
key: "{task.task().id()}",
|
||||
class: format!(
|
||||
"px-8 pt-5 {} flex flex-row gap-4",
|
||||
if task.task().deadline().is_some() {
|
||||
"pb-0.5"
|
||||
} else {
|
||||
"pb-5"
|
||||
}
|
||||
),
|
||||
TaskListItem {
|
||||
task: task.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,20 +12,24 @@ pub(crate) fn ProjectsPage() -> Element {
|
||||
rsx! {
|
||||
match projects_query.result().value() {
|
||||
QueryResult::Ok(QueryValue::Projects(projects))
|
||||
| QueryResult::Loading(Some(QueryValue::Projects(projects))) => rsx! {
|
||||
div {
|
||||
class: "flex flex-col",
|
||||
for project in projects.clone() {
|
||||
div {
|
||||
key: "{project.id()}",
|
||||
class: format!(
|
||||
"px-8 py-4 select-none {}",
|
||||
if project_being_edited().is_some_and(|p| p.id() == project.id()) {
|
||||
"bg-zinc-700"
|
||||
} else { "" }
|
||||
),
|
||||
onclick: move |_| project_being_edited.set(Some(project.clone())),
|
||||
{project.title()}
|
||||
| QueryResult::Loading(Some(QueryValue::Projects(projects))) => {
|
||||
let mut projects = projects.clone();
|
||||
projects.sort();
|
||||
rsx! {
|
||||
div {
|
||||
class: "flex flex-col",
|
||||
for project in projects {
|
||||
div {
|
||||
key: "{project.id()}",
|
||||
class: format!(
|
||||
"px-8 py-4 select-none {}",
|
||||
if project_being_edited().is_some_and(|p| p.id() == project.id()) {
|
||||
"bg-zinc-700"
|
||||
} else { "" }
|
||||
),
|
||||
onclick: move |_| project_being_edited.set(Some(project.clone())),
|
||||
{project.title()}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -64,8 +64,10 @@ pub(crate) fn SubtasksForm(task: Task) -> Element {
|
||||
match subtasks_query.result().value() {
|
||||
QueryResult::Ok(QueryValue::Subtasks(subtasks))
|
||||
| QueryResult::Loading(Some(QueryValue::Subtasks(subtasks))) => {
|
||||
let mut subtasks = subtasks.clone();
|
||||
subtasks.sort();
|
||||
|
||||
rsx! {
|
||||
for subtask in subtasks.clone() {
|
||||
for subtask in subtasks {
|
||||
div {
|
||||
![]() Optimize cloning within the rendering loop. Each iteration of the loop clones Improve error handling strategy. The use of **Optimize cloning within the rendering loop.**
Each iteration of the loop clones `subtask` and `task` multiple times, which can lead to significant performance overhead, especially with a large number of subtasks. Consider restructuring the code to minimize cloning, possibly by using references or restructuring the data flow to reduce the need for cloning.
---
**Improve error handling strategy.**
The use of `panic!` in the default case of the match statement handling query results is a risky approach that could lead to crashes in production. Consider replacing this with a more user-friendly error message or a recovery strategy that does not terminate the application.
<!-- This is an auto-generated comment by CodeRabbit -->
|
||||
key: "{subtask.id()}",
|
||||
class: "flex flex-row items-center gap-3",
|
||||
|
@ -4,6 +4,7 @@ use dioxus::core_macro::rsx;
|
||||
use dioxus::dioxus_core::Element;
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_query::prelude::use_query_client;
|
||||
use crate::components::task_list_item::TaskListItem;
|
||||
use crate::query::{QueryErrors, QueryKey, QueryValue};
|
||||
use crate::server::tasks::complete_task;
|
||||
|
||||
@ -12,6 +13,8 @@ pub(crate) fn TaskList(tasks: Vec<TaskWithSubtasks>, class: Option<&'static str>
|
||||
let query_client = use_query_client::<QueryValue, QueryErrors, QueryKey>();
|
||||
let mut task_being_edited = use_context::<Signal<Option<Task>>>();
|
||||
|
||||
tasks.sort();
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
class: format!("flex flex-col {}", class.unwrap_or("")),
|
||||
@ -74,50 +77,8 @@ pub(crate) fn TaskList(tasks: Vec<TaskWithSubtasks>, class: Option<&'static str>
|
||||
}
|
||||
}
|
||||
},
|
||||
div {
|
||||
class: "flex flex-col",
|
||||
div {
|
||||
class: "mt-1 grow font-medium",
|
||||
{task.task().title()}
|
||||
},
|
||||
div {
|
||||
class: "flex flex-row gap-4",
|
||||
if let Some(deadline) = task.task().deadline() {
|
||||
div {
|
||||
class: "text-sm text-zinc-400",
|
||||
i {
|
||||
class: "fa-solid fa-bomb"
|
||||
},
|
||||
{deadline.format(" %m. %-d.").to_string()}
|
||||
}
|
||||
}
|
||||
if let Category::Calendar { time, .. } = task.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()}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !task.subtasks().is_empty() {
|
||||
div {
|
||||
class: "text-sm text-zinc-400",
|
||||
i {
|
||||
class: "fa-solid fa-list-check"
|
||||
},
|
||||
{format!(
|
||||
" {}/{}",
|
||||
task.subtasks().iter()
|
||||
.filter(|subtask| subtask.is_completed())
|
||||
.count(),
|
||||
task.subtasks().len()
|
||||
)}
|
||||
}
|
||||
}
|
||||
}
|
||||
TaskListItem {
|
||||
task: task.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
61
src/components/task_list_item.rs
Normal file
@ -0,0 +1,61 @@
|
||||
use chrono::{Datelike, Local};
|
||||
use crate::models::category::Category;
|
||||
use crate::models::task::TaskWithSubtasks;
|
||||
use dioxus::core_macro::rsx;
|
||||
use dioxus::dioxus_core::Element;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[component]
|
||||
pub(crate) fn TaskListItem(task: TaskWithSubtasks) -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
class: "flex flex-col",
|
||||
div {
|
||||
class: "mt-1 grow font-medium",
|
||||
{task.task().title()}
|
||||
},
|
||||
div {
|
||||
class: "flex flex-row gap-4",
|
||||
if let Some(deadline) = task.task().deadline() {
|
||||
div {
|
||||
class: "text-sm text-zinc-400",
|
||||
i {
|
||||
class: "fa-solid fa-bomb"
|
||||
},
|
||||
{deadline.format(if deadline.year() == Local::now().year() {
|
||||
" %m. %-d."
|
||||
} else {
|
||||
" %m. %-d. %Y"
|
||||
}).to_string()}
|
||||
}
|
||||
}
|
||||
if let Category::Calendar { time, .. } = task.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()}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !task.subtasks().is_empty() {
|
||||
div {
|
||||
class: "text-sm text-zinc-400",
|
||||
i {
|
||||
class: "fa-solid fa-list-check"
|
||||
},
|
||||
{format!(
|
||||
" {}/{}",
|
||||
task.subtasks().iter()
|
||||
.filter(|subtask| subtask.is_completed())
|
||||
.count(),
|
||||
task.subtasks().len()
|
||||
)}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
![]() Approved: The component is well-implemented with effective use of conditional rendering and UI elements. The handling of tasks, deadlines, and subtasks is appropriate and enhances user interaction. Consider adding comments to complex conditional blocks for better maintainability and readability, especially for new developers or when revisiting the code later. **Approved: `TaskListItem` component implementation.**
The component is well-implemented with effective use of conditional rendering and UI elements. The handling of tasks, deadlines, and subtasks is appropriate and enhances user interaction.
Consider adding comments to complex conditional blocks for better maintainability and readability, especially for new developers or when revisiting the code later.
<!-- This is an auto-generated comment by CodeRabbit -->
|
@ -53,6 +53,13 @@ impl From<diesel::result::Error> for SubtaskError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ErrorVec<Error>> for ErrorVec<SubtaskError> {
|
||||
fn from(error_vec: ErrorVec<Error>) -> Self {
|
||||
Vec::from(error_vec).into_iter()
|
||||
.map(SubtaskError::Error).collect::<Vec<SubtaskError>>().into()
|
||||
}
|
||||
![]() Approved: Conversion from The implementation is correct and enhances the error handling capabilities by allowing seamless integration of generic errors into Consider adding error logging at this conversion point to aid in debugging and maintaining error traceability. **Approved: Conversion from `ErrorVec<Error>` to `ErrorVec<SubtaskError>`.**
The implementation is correct and enhances the error handling capabilities by allowing seamless integration of generic errors into `SubtaskError`. This is a valuable addition for robust error management.
Consider adding error logging at this conversion point to aid in debugging and maintaining error traceability.
<!-- This is an auto-generated comment by CodeRabbit -->
|
||||
}
|
||||
|
||||
// Has to be implemented for Dioxus server functions.
|
||||
impl Display for SubtaskError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
|
@ -5,6 +5,7 @@ mod route;
|
||||
mod schema;
|
||||
mod server;
|
||||
mod query;
|
||||
mod utils;
|
||||
|
||||
use components::app::App;
|
||||
use dioxus::prelude::*;
|
||||
|
@ -1,3 +1,4 @@
|
||||
use std::cmp::Ordering;
|
||||
use chrono::NaiveDateTime;
|
||||
use crate::schema::projects;
|
||||
use diesel::prelude::*;
|
||||
@ -35,6 +36,20 @@ impl Project {
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Project {}
|
||||
|
||||
impl PartialOrd<Self> for Project {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Project {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.title().cmp(other.title())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Insertable, Serialize, Deserialize, Validate, Clone, Debug)]
|
||||
#[diesel(table_name = projects)]
|
||||
pub struct NewProject {
|
||||
|
@ -1,3 +1,4 @@
|
||||
use std::cmp::Ordering;
|
||||
use crate::models::task::Task;
|
||||
use crate::schema::subtasks;
|
||||
use chrono::NaiveDateTime;
|
||||
@ -48,6 +49,21 @@ impl Subtask {
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Subtask {}
|
||||
|
||||
impl PartialOrd<Self> for Subtask {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Subtask {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.is_completed().cmp(&other.is_completed())
|
||||
.then(self.created_at().cmp(&other.created_at()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Insertable, Serialize, Deserialize, Validate, Clone, Debug)]
|
||||
#[diesel(table_name = subtasks)]
|
||||
pub struct NewSubtask {
|
||||
![]() Approved: The dual-layered comparison logic is well-implemented, prioritizing completion status and then ordering by creation timestamps. This is logical and efficient for the intended use case. However, consider caching the results of **Approved: `Ord` trait implementation for `Subtask`.**
The dual-layered comparison logic is well-implemented, prioritizing completion status and then ordering by creation timestamps. This is logical and efficient for the intended use case.
However, consider caching the results of `is_completed()` and `created_at()` if they are called frequently in performance-critical sections.
<!-- This is an auto-generated comment by CodeRabbit -->
|
||||
|
@ -1,10 +1,12 @@
|
||||
use chrono::NaiveDateTime;
|
||||
use crate::models::category::Category;
|
||||
use crate::models::subtask::Subtask;
|
||||
use crate::schema::tasks;
|
||||
use crate::utils::reverse_ord_option::ReverseOrdOption;
|
||||
use chrono::NaiveDateTime;
|
||||
use diesel::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Ordering;
|
||||
use validator::Validate;
|
||||
use crate::models::subtask::Subtask;
|
||||
|
||||
const TITLE_LENGTH_MIN: u64 = 1;
|
||||
const TITLE_LENGTH_MAX: u64 = 255;
|
||||
@ -52,6 +54,40 @@ impl Task {
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Task {}
|
||||
|
||||
impl PartialOrd<Self> for Task {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Task {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
match (&self.category, &other.category) {
|
||||
(Category::Inbox, Category::Inbox) => self.created_at.cmp(&other.created_at),
|
||||
(
|
||||
Category::Calendar { date: self_date, time: self_time, .. },
|
||||
Category::Calendar { date: other_date, time: other_time, .. }
|
||||
) => self_date.cmp(other_date)
|
||||
.then(ReverseOrdOption::from(
|
||||
&self_time.as_ref().map(|calendar_time| calendar_time.time())
|
||||
).cmp(&ReverseOrdOption::from(
|
||||
&other_time.as_ref().map(|calendar_time| calendar_time.time())
|
||||
)))
|
||||
.then(ReverseOrdOption::from(&self.deadline()).cmp(
|
||||
&ReverseOrdOption::from(&other.deadline())
|
||||
))
|
||||
.then(self.created_at.cmp(&other.created_at)),
|
||||
(Category::Done, Category::Done) | (Category::Trash, Category::Trash)
|
||||
=> self.updated_at.cmp(&other.updated_at).reverse(),
|
||||
(_, _) => ReverseOrdOption::from(&self.deadline()).cmp(
|
||||
&ReverseOrdOption::from(&other.deadline())
|
||||
).then(self.created_at.cmp(&other.created_at)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
|
||||
pub struct TaskWithSubtasks {
|
||||
task: Task,
|
||||
@ -72,6 +108,20 @@ impl TaskWithSubtasks {
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for TaskWithSubtasks {}
|
||||
|
||||
impl PartialOrd<Self> for TaskWithSubtasks {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for TaskWithSubtasks {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.task().cmp(other.task())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Insertable, Serialize, Deserialize, Validate, Clone, Debug)]
|
||||
#[diesel(table_name = tasks)]
|
||||
pub struct NewTask {
|
||||
|
@ -6,6 +6,7 @@ use crate::server::database_connection::establish_database_connection;
|
||||
use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl, SelectableHelper};
|
||||
use dioxus::prelude::*;
|
||||
use validator::Validate;
|
||||
use crate::server::tasks::trigger_task_updated_at;
|
||||
|
||||
#[server]
|
||||
pub(crate) async fn create_subtask(new_subtask: NewSubtask)
|
||||
@ -25,6 +26,9 @@ pub(crate) async fn create_subtask(new_subtask: NewSubtask)
|
||||
.returning(Subtask::as_returning())
|
||||
.get_result(&mut connection)
|
||||
.map_err::<ErrorVec<SubtaskError>, _>(|error| vec![error.into()].into())?;
|
||||
|
||||
trigger_task_updated_at(new_subtask.task_id).await
|
||||
.map_err::<ErrorVec<SubtaskError>, _>(|error_vec| error_vec.into())?;
|
||||
|
||||
Ok(created_subtask)
|
||||
}
|
||||
@ -35,17 +39,13 @@ pub(crate) async fn get_subtasks_of_task(filtered_task_id: i32)
|
||||
use crate::schema::subtasks::dsl::*;
|
||||
|
||||
let mut connection = establish_database_connection()
|
||||
.map_err::<ErrorVec<Error>, _>(
|
||||
|_| vec![Error::ServerInternal].into()
|
||||
)?;
|
||||
.map_err::<ErrorVec<Error>, _>(|_| vec![Error::ServerInternal].into())?;
|
||||
|
||||
let results = subtasks
|
||||
.select(Subtask::as_select())
|
||||
.filter(task_id.eq(filtered_task_id))
|
||||
.load::<Subtask>(&mut connection)
|
||||
.map_err::<ErrorVec<Error>, _>(
|
||||
|_| vec![Error::ServerInternal].into()
|
||||
)?;
|
||||
.map_err::<ErrorVec<Error>, _>(|_| vec![Error::ServerInternal].into())?;
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
@ -73,27 +73,28 @@ pub(crate) async fn edit_subtask(subtask_id: i32, new_subtask: NewSubtask)
|
||||
.get_result(&mut connection)
|
||||
.map_err::<ErrorVec<SubtaskError>, _>(|error| vec![error.into()].into())?;
|
||||
|
||||
trigger_task_updated_at(new_subtask.task_id).await
|
||||
.map_err::<ErrorVec<SubtaskError>, _>(|error_vec| error_vec.into())?;
|
||||
|
||||
Ok(updated_task)
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub(crate) async fn restore_subtasks_of_task(filtered_task_id: i32) -> Result<
|
||||
Vec<Subtask>,
|
||||
ServerFnError<ErrorVec<SubtaskError>>
|
||||
ServerFnError<ErrorVec<Error>>
|
||||
> {
|
||||
use crate::schema::subtasks::dsl::*;
|
||||
|
||||
let mut connection = establish_database_connection()
|
||||
.map_err::<ErrorVec<SubtaskError>, _>(
|
||||
|_| vec![SubtaskError::Error(Error::ServerInternal)].into()
|
||||
)?;
|
||||
.map_err::<ErrorVec<Error>, _>(|_| vec![Error::ServerInternal].into())?;
|
||||
|
||||
let updated_subtasks = diesel::update(subtasks)
|
||||
.filter(task_id.eq(filtered_task_id))
|
||||
.set(is_completed.eq(false))
|
||||
.returning(Subtask::as_returning())
|
||||
.get_results(&mut connection)
|
||||
.map_err::<ErrorVec<SubtaskError>, _>(|error| vec![error.into()].into())?;
|
||||
.map_err::<ErrorVec<Error>, _>(|error| vec![error.into()].into())?;
|
||||
|
||||
Ok(updated_subtasks)
|
||||
}
|
||||
@ -108,8 +109,12 @@ pub(crate) async fn delete_subtask(subtask_id: i32)
|
||||
let mut connection = establish_database_connection()
|
||||
.map_err::<ErrorVec<Error>, _>(|_| vec![Error::ServerInternal].into())?;
|
||||
|
||||
diesel::delete(subtasks.filter(id.eq(subtask_id))).execute(&mut connection)
|
||||
let deleted_subtask = diesel::delete(subtasks.filter(id.eq(subtask_id)))
|
||||
.returning(Subtask::as_returning())
|
||||
.get_result(&mut connection)
|
||||
.map_err::<ErrorVec<Error>, _>(|error| vec![error.into()].into())?;
|
||||
|
||||
![]()
Inconsistent error handling in The error handling for
Analysis chainApprove timestamp update integration, suggest verifying error handling. The integration of Run the following script to verify the error handling consistency: Scripts executedThe following scripts were executed for the analysis: Script:
Length of output: 1617 > [!TIP]
> Codebase Verification
**Inconsistent error handling in `delete_subtask`.**
The error handling for `trigger_task_updated_at` in `create_subtask` and `edit_subtask` is consistent, using `map_err` to convert the error type. However, `delete_subtask` does not follow this pattern, which may lead to inconsistencies in error handling across the codebase. Consider reviewing and aligning the error handling in `delete_subtask` with the other functions.
- `src/server/subtasks.rs`: Review error handling in `delete_subtask`.
<details>
<summary>Analysis chain</summary>
**Approve timestamp update integration, suggest verifying error handling.**
The integration of `trigger_task_updated_at` in `edit_subtask` is consistent with the changes made in `create_subtask`, ensuring that the parent task's timestamp is updated upon subtask modification. The error handling appears correct, but consistency across the system should be verified.
Run the following script to verify the error handling consistency:
</details>
<details>
<summary>Scripts executed</summary>
The following scripts were executed for the analysis:
Script:
```shell
#!/bin/bash
# Description: Verify error handling consistency for `trigger_task_updated_at`.
# Test: Search for error handling patterns. Expect: Consistent error mapping.
rg --type rust -A 5 $'trigger_task_updated_at'
```
Length of output: 1617
</details>
<!-- This is an auto-generated comment by CodeRabbit -->
|
||||
trigger_task_updated_at(deleted_subtask.task_id()).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use chrono::{Datelike, Days, Months, NaiveDate};
|
||||
use chrono::{Datelike, Days, Local, Months, NaiveDate};
|
||||
use crate::errors::error::Error;
|
||||
use crate::errors::error_vec::ErrorVec;
|
||||
use crate::models::task::{NewTask, Task, TaskWithSubtasks};
|
||||
@ -80,7 +80,7 @@ pub(crate) async fn get_tasks_with_subtasks_in_category(filtered_category: Categ
|
||||
ServerFnError<ErrorVec<Error>>
|
||||
> {
|
||||
use crate::schema::tasks;
|
||||
|
||||
|
||||
let mut connection = establish_database_connection()
|
||||
.map_err::<ErrorVec<Error>, _>(|_| vec![Error::ServerInternal].into())?;
|
||||
|
||||
@ -160,8 +160,7 @@ pub(crate) async fn complete_task(task_id: i32) -> Result<Task, ServerFnError<Er
|
||||
).unwrap()
|
||||
}
|
||||
}
|
||||
restore_subtasks_of_task(task_id).await
|
||||
.map_err::<ErrorVec<Error>, _>(|_| vec![Error::ServerInternal].into())?;
|
||||
restore_subtasks_of_task(task_id).await?;
|
||||
} else {
|
||||
new_task.category = Category::Done;
|
||||
}
|
||||
@ -187,3 +186,21 @@ pub(crate) async fn delete_task(task_id: i32)
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn trigger_task_updated_at(task_id: i32) -> Result<Task, ErrorVec<Error>> {
|
||||
use crate::schema::tasks::dsl::*;
|
||||
|
||||
let mut connection = establish_database_connection()
|
||||
.map_err::<ErrorVec<Error>, _>(
|
||||
|_| vec![Error::ServerInternal].into()
|
||||
)?;
|
||||
|
||||
let updated_task = diesel::update(tasks)
|
||||
.filter(id.eq(task_id))
|
||||
.set(updated_at.eq(Local::now().naive_local()))
|
||||
.returning(Task::as_returning())
|
||||
.get_result(&mut connection)
|
||||
.map_err::<ErrorVec<Error>, _>(|error| vec![error.into()].into())?;
|
||||
|
||||
Ok(updated_task)
|
||||
}
|
||||
|
1
src/utils/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub(crate) mod reverse_ord_option;
|
||||
![]() Approve module declaration but recommend adding documentation. The module declaration Would you like me to help draft initial documentation for this module? **Approve module declaration but recommend adding documentation.**
The module declaration `pub(crate) mod reverse_ord_option;` is correctly scoped for internal crate usage. However, it would be beneficial to add documentation to explain the purpose and functionality of this module, especially since it is a new addition to the codebase.
Would you like me to help draft initial documentation for this module?
<!-- This is an auto-generated comment by CodeRabbit -->
|
31
src/utils/reverse_ord_option.rs
Normal file
@ -0,0 +1,31 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
/* The default ordering of `Option`s is `None` being less than `Some`. The purpose of this struct is
|
||||
to reverse that. */
|
||||
#[derive(PartialEq)]
|
||||
pub(crate) struct ReverseOrdOption<'a, T>(&'a Option<T>);
|
||||
|
||||
impl<'a, T: Ord> Eq for ReverseOrdOption<'a, T> {}
|
||||
|
||||
impl<'a, T: Ord> PartialOrd<Self> for ReverseOrdOption<'a, T> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Ord> Ord for ReverseOrdOption<'a, T> {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
match (self.0.as_ref(), other.0.as_ref()) {
|
||||
(None, None) => Ordering::Equal,
|
||||
(None, Some(_)) => Ordering::Greater,
|
||||
(Some(_), None) => Ordering::Less,
|
||||
(Some(self_time), Some(other_time)) => self_time.cmp(other_time)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> From<&'a Option<T>> for ReverseOrdOption<'a, T> {
|
||||
fn from(value: &'a Option<T>) -> Self {
|
||||
ReverseOrdOption(value)
|
||||
}
|
||||
}
|
Consider performance implications of cloning and sorting subtasks.
The addition of sorting to the subtasks is a great feature for improving the user interface. However, cloning and then sorting the entire list of subtasks could be performance-intensive, especially with a large number of subtasks. Consider optimizing this by sorting the subtasks in place if the data structure allows, or by using more efficient sorting algorithms or data structures that maintain order.