feat: ability to manage subtasks #40
@ -12,3 +12,4 @@ pub(crate) mod category_input;
|
||||
pub(crate) mod reoccurrence_input;
|
||||
pub(crate) mod layout;
|
||||
pub(crate) mod navigation_item;
|
||||
pub(crate) mod subtasks_form;
|
||||
|
158
src/components/subtasks_form.rs
Normal file
158
src/components/subtasks_form.rs
Normal file
@ -0,0 +1,158 @@
|
||||
use crate::models::subtask::NewSubtask;
|
||||
use crate::query::subtasks::use_subtasks_of_task_query;
|
||||
use crate::query::{QueryErrors, QueryKey, QueryValue};
|
||||
use crate::server::subtasks::{create_subtask, delete_subtask, edit_subtask};
|
||||
use dioxus::core_macro::{component, rsx};
|
||||
use dioxus::dioxus_core::Element;
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_query::prelude::{use_query_client, QueryResult};
|
||||
|
||||
#[component]
|
||||
pub(crate) fn SubtasksForm(task_id: i32) -> Element {
|
||||
let query_client = use_query_client::<QueryValue, QueryErrors, QueryKey>();
|
||||
let subtasks_query = use_subtasks_of_task_query(task_id);
|
||||
|
||||
let mut new_title = use_signal(String::new);
|
||||
|
||||
rsx! {
|
||||
form {
|
||||
class: "flex flex-row items-center gap-3",
|
||||
onsubmit: move |event| async move {
|
||||
let new_subtask = NewSubtask::new(
|
||||
task_id,
|
||||
event.values().get("title").unwrap().as_value(),
|
||||
false
|
||||
);
|
||||
let _ = create_subtask(new_subtask).await;
|
||||
query_client.invalidate_queries(&[QueryKey::SubtasksOfTaskId(task_id)]);
|
||||
new_title.set(String::new());
|
||||
},
|
||||
label {
|
||||
r#for: "input_new_title",
|
||||
class: "min-w-6 text-center",
|
||||
i {
|
||||
class: "fa-solid fa-list-check text-zinc-400/50"
|
||||
}
|
||||
}
|
||||
div {
|
||||
class: "grow grid grid-cols-6 gap-2",
|
||||
input {
|
||||
name: "title",
|
||||
required: true,
|
||||
value: new_title,
|
||||
r#type: "text",
|
||||
class: "grow py-2 px-3 col-span-5 bg-zinc-800/50 rounded-lg",
|
||||
id: "input_new_title",
|
||||
onchange: move |event| new_title.set(event.value())
|
||||
}
|
||||
button {
|
||||
r#type: "submit",
|
||||
class: "py-2 col-span-1 bg-zinc-800/50 rounded-lg",
|
||||
i {
|
||||
class: "fa-solid fa-plus"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
match subtasks_query.result().value() {
|
||||
QueryResult::Ok(QueryValue::Subtasks(subtasks))
|
||||
| QueryResult::Loading(Some(QueryValue::Subtasks(subtasks))) => {
|
||||
rsx! {
|
||||
for subtask in subtasks.clone() {
|
||||
div {
|
||||
key: "{subtask.id()}",
|
||||
class: "flex flex-row items-center gap-3",
|
||||
i {
|
||||
class: format!(
|
||||
"{} min-w-6 text-center text-2xl text-zinc-400/50",
|
||||
if subtask.is_completed() {
|
||||
"fa solid fa-square-check"
|
||||
} else {
|
||||
"fa-regular fa-square"
|
||||
}
|
||||
),
|
||||
onclick: {
|
||||
let subtask = subtask.clone();
|
||||
move |_| {
|
||||
let subtask = subtask.clone();
|
||||
async move {
|
||||
let new_subtask = NewSubtask::new(
|
||||
subtask.task_id(),
|
||||
subtask.title().to_owned(),
|
||||
!subtask.is_completed()
|
||||
);
|
||||
let _ = edit_subtask(
|
||||
subtask.id(),
|
||||
new_subtask
|
||||
).await;
|
||||
query_client.invalidate_queries(&[
|
||||
QueryKey::SubtasksOfTaskId(task_id)
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
div {
|
||||
class: "grow grid grid-cols-6 gap-2",
|
||||
input {
|
||||
r#type: "text",
|
||||
class: "grow py-2 px-3 col-span-5 bg-zinc-800/50 rounded-lg",
|
||||
id: "input_new_title",
|
||||
initial_value: subtask.title(),
|
||||
onchange: {
|
||||
let subtask = subtask.clone();
|
||||
move |event| {
|
||||
let subtask = subtask.clone();
|
||||
async move {
|
||||
let new_subtask = NewSubtask::new(
|
||||
subtask.task_id(),
|
||||
event.value(),
|
||||
subtask.is_completed()
|
||||
);
|
||||
let _ = edit_subtask(
|
||||
subtask.id(),
|
||||
new_subtask
|
||||
).await;
|
||||
query_client.invalidate_queries(&[
|
||||
QueryKey::SubtasksOfTaskId(task_id)
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
button {
|
||||
r#type: "button",
|
||||
class: "py-2 col-span-1 bg-zinc-800/50 rounded-lg",
|
||||
onclick: {
|
||||
let subtask = subtask.clone();
|
||||
move |_| {
|
||||
let subtask = subtask.clone();
|
||||
async move {
|
||||
let _ = delete_subtask(subtask.id()).await;
|
||||
query_client.invalidate_queries(&[
|
||||
QueryKey::SubtasksOfTaskId(task_id)
|
||||
]);
|
||||
}
|
||||
}
|
||||
},
|
||||
i {
|
||||
class: "fa-solid fa-trash-can"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
QueryResult::Loading(None) => rsx! {
|
||||
// TODO: Add a loading indicator.
|
||||
},
|
||||
QueryResult::Err(errors) => rsx! {
|
||||
div {
|
||||
"Errors occurred: {errors:?}"
|
||||
}
|
||||
},
|
||||
value => panic!("Unexpected query result: {value:?}")
|
||||
}
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ use dioxus::core_macro::{component, rsx};
|
||||
use dioxus::dioxus_core::Element;
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_query::prelude::use_query_client;
|
||||
use crate::components::subtasks_form::SubtasksForm;
|
||||
|
||||
const REMINDER_OFFSETS: [Option<Duration>; 17] = [
|
||||
None,
|
||||
@ -79,262 +80,274 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()
|
||||
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(),
|
||||
event.values().get("deadline").unwrap().as_value().parse().ok(),
|
||||
match &selected_category() {
|
||||
Category::WaitingFor(_) => Category::WaitingFor(
|
||||
event.values().get("category_waiting_for").unwrap()
|
||||
.as_value()
|
||||
),
|
||||
Category::Calendar { .. } => Category::Calendar {
|
||||
date: event.values().get("category_calendar_date").unwrap()
|
||||
.as_value().parse().unwrap(),
|
||||
reoccurrence: category_calendar_reoccurrence_interval().map(
|
||||
|reoccurrence_interval| Reoccurrence::new(
|
||||
event.values().get("category_calendar_date").unwrap()
|
||||
.as_value().parse().unwrap(),
|
||||
reoccurrence_interval,
|
||||
event.values().get("category_calendar_reoccurrence_length")
|
||||
.unwrap().as_value().parse().unwrap()
|
||||
)
|
||||
),
|
||||
time: event.values().get("category_calendar_time").unwrap()
|
||||
.as_value().parse().ok().map(|time|
|
||||
CalendarTime::new(
|
||||
time,
|
||||
REMINDER_OFFSETS[
|
||||
event.values()
|
||||
.get("category_calendar_reminder_offset_index").unwrap()
|
||||
.as_value().parse::<usize>().unwrap()
|
||||
]
|
||||
)
|
||||
)
|
||||
},
|
||||
category => category.clone()
|
||||
},
|
||||
event.values().get("project_id").unwrap()
|
||||
.as_value().parse::<i32>().ok().filter(|&id| id > 0),
|
||||
);
|
||||
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())
|
||||
]);
|
||||
on_successful_submit.call(());
|
||||
}
|
||||
},
|
||||
div {
|
||||
class: "p-4 flex flex-col gap-4",
|
||||
div {
|
||||
class: "flex flex-row items-center gap-3",
|
||||
label {
|
||||
r#for: "input_title",
|
||||
class: "min-w-6 text-center",
|
||||
i {
|
||||
class: "fa-solid fa-pen-clip text-zinc-400/50"
|
||||
},
|
||||
},
|
||||
input {
|
||||
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"
|
||||
},
|
||||
},
|
||||
div {
|
||||
class: "flex flex-row items-center gap-3",
|
||||
label {
|
||||
r#for: "input_project",
|
||||
class: "min-w-6 text-center",
|
||||
i {
|
||||
class: "fa-solid fa-list text-zinc-400/50"
|
||||
}
|
||||
},
|
||||
select {
|
||||
name: "project_id",
|
||||
class: "px-3.5 py-2.5 bg-zinc-800/50 rounded-lg grow",
|
||||
id: "input_project",
|
||||
option {
|
||||
value: 0,
|
||||
"None"
|
||||
},
|
||||
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()}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
div {
|
||||
class: "flex flex-row items-center gap-3",
|
||||
label {
|
||||
r#for: "input_deadline",
|
||||
class: "min-w-6 text-center",
|
||||
i {
|
||||
class: "fa-solid fa-bomb text-zinc-400/50"
|
||||
}
|
||||
},
|
||||
input {
|
||||
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"
|
||||
}
|
||||
},
|
||||
div {
|
||||
class: "flex flex-row items-center gap-3",
|
||||
label {
|
||||
class: "min-w-6 text-center",
|
||||
i {
|
||||
class: "fa-solid fa-layer-group text-zinc-400/50"
|
||||
}
|
||||
},
|
||||
CategoryInput {
|
||||
selected_category: selected_category,
|
||||
class: "grow"
|
||||
}
|
||||
}
|
||||
match selected_category() {
|
||||
Category::WaitingFor(waiting_for) => rsx! {
|
||||
div {
|
||||
class: "flex flex-row items-center gap-3",
|
||||
label {
|
||||
r#for: "input_deadline",
|
||||
class: "min-w-6 text-center",
|
||||
i {
|
||||
class: "fa-solid fa-hourglass-end text-zinc-400/50"
|
||||
}
|
||||
},
|
||||
input {
|
||||
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 { date, reoccurrence, time } => rsx! {
|
||||
div {
|
||||
class: "flex flex-row items-center gap-3",
|
||||
label {
|
||||
r#for: "input_category_calendar_date",
|
||||
class: "min-w-6 text-center",
|
||||
i {
|
||||
class: "fa-solid fa-clock text-zinc-400/50"
|
||||
}
|
||||
},
|
||||
div {
|
||||
class: "grow flex flex-row gap-2",
|
||||
input {
|
||||
r#type: "date",
|
||||
name: "category_calendar_date",
|
||||
required: true,
|
||||
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()
|
||||
form {
|
||||
class: "flex flex-col gap-4",
|
||||
id: "form_task",
|
||||
onsubmit: move |event| {
|
||||
let task = task_for_submit.clone();
|
||||
async move {
|
||||
let new_task = NewTask::new(
|
||||
event.values().get("title").unwrap().as_value(),
|
||||
event.values().get("deadline").unwrap().as_value().parse().ok(),
|
||||
match &selected_category() {
|
||||
Category::WaitingFor(_) => Category::WaitingFor(
|
||||
event.values().get("category_waiting_for").unwrap()
|
||||
.as_value()
|
||||
),
|
||||
class: "py-2 px-3 bg-zinc-800/50 rounded-lg grow",
|
||||
id: "input_category_calendar_time",
|
||||
oninput: move |event| {
|
||||
category_calendar_has_time.set(!event.value().is_empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
div {
|
||||
class: "flex flex-row items-center gap-3",
|
||||
label {
|
||||
r#for: "category_calendar_reoccurrence_length",
|
||||
class: "min-w-6 text-center",
|
||||
i {
|
||||
class: "fa-solid fa-repeat text-zinc-400/50"
|
||||
}
|
||||
},
|
||||
div {
|
||||
class: "grow grid grid-cols-6 gap-2",
|
||||
ReoccurrenceIntervalInput {
|
||||
reoccurrence_interval: category_calendar_reoccurrence_interval
|
||||
Category::Calendar { .. } => Category::Calendar {
|
||||
date: event.values().get("category_calendar_date").unwrap()
|
||||
.as_value().parse().unwrap(),
|
||||
reoccurrence: category_calendar_reoccurrence_interval().map(
|
||||
|reoccurrence_interval| Reoccurrence::new(
|
||||
event.values().get("category_calendar_date").unwrap()
|
||||
.as_value().parse().unwrap(),
|
||||
reoccurrence_interval,
|
||||
event.values()
|
||||
.get("category_calendar_reoccurrence_length")
|
||||
.unwrap().as_value().parse().unwrap()
|
||||
)
|
||||
),
|
||||
time: event.values().get("category_calendar_time").unwrap()
|
||||
.as_value().parse().ok().map(|time|
|
||||
CalendarTime::new(
|
||||
time,
|
||||
REMINDER_OFFSETS[
|
||||
event.values()
|
||||
.get("category_calendar_reminder_offset_index")
|
||||
.unwrap().as_value().parse::<usize>().unwrap()
|
||||
]
|
||||
)
|
||||
)
|
||||
},
|
||||
category => category.clone()
|
||||
},
|
||||
input {
|
||||
r#type: "number",
|
||||
inputmode: "numeric",
|
||||
name: "category_calendar_reoccurrence_length",
|
||||
disabled: category_calendar_reoccurrence_interval().is_none(),
|
||||
required: true,
|
||||
min: 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"
|
||||
event.values().get("project_id").unwrap()
|
||||
.as_value().parse::<i32>().ok().filter(|&id| id > 0),
|
||||
);
|
||||
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())
|
||||
]);
|
||||
on_successful_submit.call(());
|
||||
}
|
||||
},
|
||||
div {
|
||||
class: "flex flex-row items-center gap-3",
|
||||
label {
|
||||
r#for: "input_title",
|
||||
class: "min-w-6 text-center",
|
||||
i {
|
||||
class: "fa-solid fa-pen-clip text-zinc-400/50"
|
||||
},
|
||||
},
|
||||
input {
|
||||
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"
|
||||
},
|
||||
},
|
||||
div {
|
||||
class: "flex flex-row items-center gap-3",
|
||||
label {
|
||||
r#for: "input_project",
|
||||
class: "min-w-6 text-center",
|
||||
i {
|
||||
class: "fa-solid fa-list text-zinc-400/50"
|
||||
}
|
||||
},
|
||||
select {
|
||||
name: "project_id",
|
||||
class: "px-3.5 py-2.5 bg-zinc-800/50 rounded-lg grow",
|
||||
id: "input_project",
|
||||
option {
|
||||
value: 0,
|
||||
"None"
|
||||
},
|
||||
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()}
|
||||
}
|
||||
}
|
||||
},
|
||||
if category_calendar_has_time() {
|
||||
},
|
||||
div {
|
||||
class: "flex flex-row items-center gap-3",
|
||||
label {
|
||||
r#for: "input_deadline",
|
||||
class: "min-w-6 text-center",
|
||||
i {
|
||||
class: "fa-solid fa-bomb text-zinc-400/50"
|
||||
}
|
||||
},
|
||||
input {
|
||||
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"
|
||||
}
|
||||
},
|
||||
div {
|
||||
class: "flex flex-row items-center gap-3",
|
||||
label {
|
||||
class: "min-w-6 text-center",
|
||||
i {
|
||||
class: "fa-solid fa-layer-group text-zinc-400/50"
|
||||
}
|
||||
},
|
||||
CategoryInput {
|
||||
selected_category: selected_category,
|
||||
class: "grow"
|
||||
}
|
||||
}
|
||||
match selected_category() {
|
||||
Category::WaitingFor(waiting_for) => rsx! {
|
||||
div {
|
||||
class: "flex flex-row items-center gap-3",
|
||||
label {
|
||||
r#for: "category_calendar_reminder_offset_index",
|
||||
r#for: "input_deadline",
|
||||
class: "min-w-6 text-center",
|
||||
i {
|
||||
class: "fa-solid fa-bell text-zinc-400/50"
|
||||
class: "fa-solid fa-hourglass-end text-zinc-400/50"
|
||||
}
|
||||
},
|
||||
input {
|
||||
r#type: "range",
|
||||
name: "category_calendar_reminder_offset_index",
|
||||
min: 0,
|
||||
max: 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| {
|
||||
category_calendar_reminder_offset_index.set(
|
||||
event.value().parse().unwrap()
|
||||
);
|
||||
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 { date, reoccurrence, time } => rsx! {
|
||||
div {
|
||||
class: "flex flex-row items-center gap-3",
|
||||
label {
|
||||
r#for: "input_category_calendar_date",
|
||||
class: "min-w-6 text-center",
|
||||
i {
|
||||
class: "fa-solid fa-clock text-zinc-400/50"
|
||||
}
|
||||
},
|
||||
label {
|
||||
r#for: "category_calendar_reminder_offset_index",
|
||||
class: "pr-3 min-w-16 text-right",
|
||||
{REMINDER_OFFSETS[category_calendar_reminder_offset_index()].map(
|
||||
|offset| if offset.num_hours() < 1 {
|
||||
format!("{} min", offset.num_minutes())
|
||||
} else {
|
||||
format!("{} h", offset.num_hours())
|
||||
div {
|
||||
class: "grow flex flex-row gap-2",
|
||||
input {
|
||||
r#type: "date",
|
||||
name: "category_calendar_date",
|
||||
required: true,
|
||||
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| {
|
||||
category_calendar_has_time.set(!event.value().is_empty());
|
||||
}
|
||||
).unwrap_or_else(|| "none".to_string())}
|
||||
}
|
||||
}
|
||||
},
|
||||
div {
|
||||
class: "flex flex-row items-center gap-3",
|
||||
label {
|
||||
r#for: "category_calendar_reoccurrence_length",
|
||||
class: "min-w-6 text-center",
|
||||
i {
|
||||
class: "fa-solid fa-repeat text-zinc-400/50"
|
||||
}
|
||||
},
|
||||
div {
|
||||
class: "grow grid grid-cols-6 gap-2",
|
||||
ReoccurrenceIntervalInput {
|
||||
reoccurrence_interval: category_calendar_reoccurrence_interval
|
||||
},
|
||||
input {
|
||||
r#type: "number",
|
||||
inputmode: "numeric",
|
||||
name: "category_calendar_reoccurrence_length",
|
||||
disabled: category_calendar_reoccurrence_interval().is_none(),
|
||||
required: true,
|
||||
min: 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"
|
||||
}
|
||||
}
|
||||
},
|
||||
if category_calendar_has_time() {
|
||||
div {
|
||||
class: "flex flex-row items-center gap-3",
|
||||
label {
|
||||
r#for: "category_calendar_reminder_offset_index",
|
||||
class: "min-w-6 text-center",
|
||||
i {
|
||||
class: "fa-solid fa-bell text-zinc-400/50"
|
||||
}
|
||||
},
|
||||
input {
|
||||
r#type: "range",
|
||||
name: "category_calendar_reminder_offset_index",
|
||||
min: 0,
|
||||
max: 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| {
|
||||
category_calendar_reminder_offset_index.set(
|
||||
event.value().parse().unwrap()
|
||||
);
|
||||
}
|
||||
},
|
||||
label {
|
||||
r#for: "category_calendar_reminder_offset_index",
|
||||
class: "pr-3 min-w-16 text-right",
|
||||
{REMINDER_OFFSETS[category_calendar_reminder_offset_index()].map(
|
||||
|offset| if offset.num_hours() < 1 {
|
||||
format!("{} min", offset.num_minutes())
|
||||
} else {
|
||||
format!("{} h", offset.num_hours())
|
||||
}
|
||||
).unwrap_or_else(|| "none".to_string())}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => None
|
||||
},
|
||||
_ => None
|
||||
}
|
||||
},
|
||||
if let Some(task) = task.as_ref() {
|
||||
SubtasksForm {
|
||||
task_id: task.id()
|
||||
}
|
||||
}
|
||||
div {
|
||||
class: "flex flex-row justify-between mt-auto",
|
||||
button {
|
||||
@ -353,10 +366,10 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()
|
||||
Category::Trash,
|
||||
task.project_id()
|
||||
);
|
||||
|
||||
|
||||
let _ = edit_task(task.id(), new_task).await;
|
||||
}
|
||||
|
||||
|
||||
query_client.invalidate_queries(&[
|
||||
QueryKey::TasksInCategory(task.category().clone()),
|
||||
QueryKey::Tasks,
|
||||
@ -370,6 +383,7 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()
|
||||
}
|
||||
}
|
||||
button {
|
||||
form: "form_task",
|
||||
r#type: "submit",
|
||||
class: "py-2 px-4 bg-zinc-300/50 rounded-lg",
|
||||
i {
|
||||
|
@ -15,86 +15,94 @@ pub(crate) fn TaskList(tasks: Vec<Task>, class: Option<&'static str>) -> Element
|
||||
rsx! {
|
||||
div {
|
||||
class: format!("flex flex-col {}", class.unwrap_or("")),
|
||||
{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() {
|
||||
for task in tasks.clone() {
|
||||
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 if let Category::Calendar { time, .. } = task.category() {
|
||||
if time.is_some() {
|
||||
"pb-0.5"
|
||||
} else {
|
||||
"pb-5"
|
||||
}
|
||||
} else {
|
||||
"pb-5"
|
||||
},
|
||||
if task_being_edited().is_some_and(|t| t.id() == task.id()) {
|
||||
"bg-zinc-700"
|
||||
} else { "" }
|
||||
}
|
||||
} else {
|
||||
"pb-5"
|
||||
},
|
||||
if task_being_edited().is_some_and(|t| t.id() == task.id()) {
|
||||
"bg-zinc-700"
|
||||
} else { "" }
|
||||
),
|
||||
onclick: {
|
||||
let task = task.clone();
|
||||
move |_| task_being_edited.set(Some(task.clone()))
|
||||
},
|
||||
i {
|
||||
class: format!(
|
||||
"{} text-3xl text-zinc-500",
|
||||
if *(task.category()) == Category::Done {
|
||||
"fa solid fa-square-check"
|
||||
} else {
|
||||
"fa-regular fa-square"
|
||||
}
|
||||
),
|
||||
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| {
|
||||
onclick: {
|
||||
let task = task.clone();
|
||||
move |event| {
|
||||
// To prevent editing the task.
|
||||
event.stop_propagation();
|
||||
let task = task_clone.clone();
|
||||
let task = task.clone();
|
||||
async move {
|
||||
let completed_task = complete_task(task.id()).await;
|
||||
query_client.invalidate_queries(&[
|
||||
QueryKey::Tasks,
|
||||
let mut query_keys = vec![
|
||||
QueryKey::Tasks,
|
||||
QueryKey::TasksInCategory(
|
||||
completed_task.unwrap().category().clone()
|
||||
),
|
||||
]);
|
||||
)
|
||||
];
|
||||
if let Category::Calendar { reoccurrence: Some(_), .. }
|
||||
= task.category() {
|
||||
query_keys.push(QueryKey::SubtasksOfTaskId(task.id()));
|
||||
}
|
||||
query_client.invalidate_queries(&query_keys);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
div {
|
||||
class: "flex flex-col",
|
||||
div {
|
||||
class: "mt-1 grow font-medium",
|
||||
{task.title()}
|
||||
},
|
||||
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() {
|
||||
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 let Category::Calendar { time, .. } = task.category() {
|
||||
if let Some(calendar_time) = time {
|
||||
div {
|
||||
class: "text-sm text-zinc-400",
|
||||
i {
|
||||
class: "fa-solid fa-bomb"
|
||||
class: "fa-solid fa-clock"
|
||||
},
|
||||
{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()}
|
||||
}
|
||||
{calendar_time.time().format(" %k:%M").to_string()}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user
Consider avoiding cloning the entire tasks vector.
Cloning the entire
tasks
vector might lead to performance issues, especially if the vector is large. Consider iterating over references if the task objects do not need to be owned within the loop.