feat: list sorting (#42)
This commit is contained in:
commit
fa68b05738
@ -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 {
|
||||
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
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()
|
||||
)}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
@ -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())?;
|
||||
|
||||
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
1
src/utils/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub(crate) mod reverse_ord_option;
|
31
src/utils/reverse_ord_option.rs
Normal file
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)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user