feat: create a from for subtasks
This commit is contained in:
parent
11bab284d4
commit
a05b4f9f66
@ -12,3 +12,4 @@ pub(crate) mod category_input;
|
|||||||
pub(crate) mod reoccurrence_input;
|
pub(crate) mod reoccurrence_input;
|
||||||
pub(crate) mod layout;
|
pub(crate) mod layout;
|
||||||
pub(crate) mod navigation_item;
|
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::dioxus_core::Element;
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_query::prelude::use_query_client;
|
use dioxus_query::prelude::use_query_client;
|
||||||
|
use crate::components::subtasks_form::SubtasksForm;
|
||||||
|
|
||||||
const REMINDER_OFFSETS: [Option<Duration>; 17] = [
|
const REMINDER_OFFSETS: [Option<Duration>; 17] = [
|
||||||
None,
|
None,
|
||||||
@ -79,262 +80,274 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()
|
|||||||
let task_for_submit = task.clone();
|
let task_for_submit = task.clone();
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
form {
|
div {
|
||||||
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(());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
class: "p-4 flex flex-col gap-4",
|
class: "p-4 flex flex-col gap-4",
|
||||||
div {
|
form {
|
||||||
class: "flex flex-row items-center gap-3",
|
class: "flex flex-col gap-4",
|
||||||
label {
|
id: "form_task",
|
||||||
r#for: "input_title",
|
onsubmit: move |event| {
|
||||||
class: "min-w-6 text-center",
|
let task = task_for_submit.clone();
|
||||||
i {
|
async move {
|
||||||
class: "fa-solid fa-pen-clip text-zinc-400/50"
|
let new_task = NewTask::new(
|
||||||
},
|
event.values().get("title").unwrap().as_value(),
|
||||||
},
|
event.values().get("deadline").unwrap().as_value().parse().ok(),
|
||||||
input {
|
match &selected_category() {
|
||||||
name: "title",
|
Category::WaitingFor(_) => Category::WaitingFor(
|
||||||
required: true,
|
event.values().get("category_waiting_for").unwrap()
|
||||||
initial_value: task.as_ref().map(|task| task.title().to_owned()),
|
.as_value()
|
||||||
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()
|
|
||||||
),
|
),
|
||||||
class: "py-2 px-3 bg-zinc-800/50 rounded-lg grow",
|
Category::Calendar { .. } => Category::Calendar {
|
||||||
id: "input_category_calendar_time",
|
date: event.values().get("category_calendar_date").unwrap()
|
||||||
oninput: move |event| {
|
.as_value().parse().unwrap(),
|
||||||
category_calendar_has_time.set(!event.value().is_empty());
|
reoccurrence: category_calendar_reoccurrence_interval().map(
|
||||||
}
|
|reoccurrence_interval| Reoccurrence::new(
|
||||||
}
|
event.values().get("category_calendar_date").unwrap()
|
||||||
}
|
.as_value().parse().unwrap(),
|
||||||
},
|
reoccurrence_interval,
|
||||||
div {
|
event.values()
|
||||||
class: "flex flex-row items-center gap-3",
|
.get("category_calendar_reoccurrence_length")
|
||||||
label {
|
.unwrap().as_value().parse().unwrap()
|
||||||
r#for: "category_calendar_reoccurrence_length",
|
)
|
||||||
class: "min-w-6 text-center",
|
),
|
||||||
i {
|
time: event.values().get("category_calendar_time").unwrap()
|
||||||
class: "fa-solid fa-repeat text-zinc-400/50"
|
.as_value().parse().ok().map(|time|
|
||||||
}
|
CalendarTime::new(
|
||||||
},
|
time,
|
||||||
div {
|
REMINDER_OFFSETS[
|
||||||
class: "grow grid grid-cols-6 gap-2",
|
event.values()
|
||||||
ReoccurrenceIntervalInput {
|
.get("category_calendar_reminder_offset_index")
|
||||||
reoccurrence_interval: category_calendar_reoccurrence_interval
|
.unwrap().as_value().parse::<usize>().unwrap()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
category => category.clone()
|
||||||
},
|
},
|
||||||
input {
|
event.values().get("project_id").unwrap()
|
||||||
r#type: "number",
|
.as_value().parse::<i32>().ok().filter(|&id| id > 0),
|
||||||
inputmode: "numeric",
|
);
|
||||||
name: "category_calendar_reoccurrence_length",
|
if let Some(task) = task {
|
||||||
disabled: category_calendar_reoccurrence_interval().is_none(),
|
let _ = edit_task(task.id(), new_task).await;
|
||||||
required: true,
|
} else {
|
||||||
min: 1,
|
let _ = create_task(new_task).await;
|
||||||
initial_value: category_calendar_reoccurrence_interval()
|
}
|
||||||
.map_or(String::new(), |_| reoccurrence.map_or(1, |reoccurrence|
|
query_client.invalidate_queries(&[
|
||||||
reoccurrence.length()).to_string()),
|
QueryKey::Tasks,
|
||||||
class: "py-2 px-3 bg-zinc-800/50 rounded-lg col-span-2 text-right",
|
QueryKey::TasksInCategory(selected_category())
|
||||||
id: "category_calendar_reoccurrence_length"
|
]);
|
||||||
|
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 {
|
div {
|
||||||
class: "flex flex-row items-center gap-3",
|
class: "flex flex-row items-center gap-3",
|
||||||
label {
|
label {
|
||||||
r#for: "category_calendar_reminder_offset_index",
|
r#for: "input_deadline",
|
||||||
class: "min-w-6 text-center",
|
class: "min-w-6 text-center",
|
||||||
i {
|
i {
|
||||||
class: "fa-solid fa-bell text-zinc-400/50"
|
class: "fa-solid fa-hourglass-end text-zinc-400/50"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
input {
|
input {
|
||||||
r#type: "range",
|
name: "category_waiting_for",
|
||||||
name: "category_calendar_reminder_offset_index",
|
required: true,
|
||||||
min: 0,
|
initial_value: waiting_for,
|
||||||
max: REMINDER_OFFSETS.len() as i64 - 1,
|
r#type: "text",
|
||||||
initial_value: category_calendar_reminder_offset_index()
|
class: "py-2 px-3 bg-zinc-800/50 rounded-lg grow",
|
||||||
.to_string(),
|
id: "input_category_waiting_for"
|
||||||
class: "grow input-range-reverse",
|
},
|
||||||
id: "category_calendar_has_reminder",
|
}
|
||||||
oninput: move |event| {
|
},
|
||||||
category_calendar_reminder_offset_index.set(
|
Category::Calendar { date, reoccurrence, time } => rsx! {
|
||||||
event.value().parse().unwrap()
|
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 {
|
div {
|
||||||
r#for: "category_calendar_reminder_offset_index",
|
class: "grow flex flex-row gap-2",
|
||||||
class: "pr-3 min-w-16 text-right",
|
input {
|
||||||
{REMINDER_OFFSETS[category_calendar_reminder_offset_index()].map(
|
r#type: "date",
|
||||||
|offset| if offset.num_hours() < 1 {
|
name: "category_calendar_date",
|
||||||
format!("{} min", offset.num_minutes())
|
required: true,
|
||||||
} else {
|
initial_value: date.format("%Y-%m-%d").to_string(),
|
||||||
format!("{} h", offset.num_hours())
|
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 {
|
div {
|
||||||
class: "flex flex-row justify-between mt-auto",
|
class: "flex flex-row justify-between mt-auto",
|
||||||
button {
|
button {
|
||||||
@ -370,6 +383,7 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
|
form: "form_task",
|
||||||
r#type: "submit",
|
r#type: "submit",
|
||||||
class: "py-2 px-4 bg-zinc-300/50 rounded-lg",
|
class: "py-2 px-4 bg-zinc-300/50 rounded-lg",
|
||||||
i {
|
i {
|
||||||
|
@ -15,86 +15,94 @@ pub(crate) fn TaskList(tasks: Vec<Task>, class: Option<&'static str>) -> Element
|
|||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
class: format!("flex flex-col {}", class.unwrap_or("")),
|
class: format!("flex flex-col {}", class.unwrap_or("")),
|
||||||
{tasks.iter().cloned().map(|task| {
|
for task in tasks.clone() {
|
||||||
let task_clone = task.clone();
|
div {
|
||||||
rsx! {
|
key: "{task.id()}",
|
||||||
div {
|
class: format!(
|
||||||
key: "{task.id()}",
|
"px-8 pt-5 {} flex flex-row gap-4 select-none {}",
|
||||||
class: format!(
|
if task.deadline().is_some() {
|
||||||
"px-8 pt-5 {} flex flex-row gap-4 select-none {}",
|
"pb-0.5"
|
||||||
if task.deadline().is_some() {
|
} else if let Category::Calendar { time, .. } = task.category() {
|
||||||
|
if time.is_some() {
|
||||||
"pb-0.5"
|
"pb-0.5"
|
||||||
} else if let Category::Calendar { time, .. } = task.category() {
|
|
||||||
if time.is_some() {
|
|
||||||
"pb-0.5"
|
|
||||||
} else {
|
|
||||||
"pb-5"
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
"pb-5"
|
"pb-5"
|
||||||
},
|
}
|
||||||
if task_being_edited().is_some_and(|t| t.id() == task.id()) {
|
} else {
|
||||||
"bg-zinc-700"
|
"pb-5"
|
||||||
} else { "" }
|
},
|
||||||
|
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())),
|
onclick: {
|
||||||
i {
|
let task = task.clone();
|
||||||
class: format!(
|
move |event| {
|
||||||
"{} text-3xl text-zinc-500",
|
|
||||||
if *(task_clone.category()) == Category::Done {
|
|
||||||
"fa solid fa-square-check"
|
|
||||||
} else {
|
|
||||||
"fa-regular fa-square"
|
|
||||||
}
|
|
||||||
),
|
|
||||||
onclick: move |event| {
|
|
||||||
// To prevent editing the task.
|
// To prevent editing the task.
|
||||||
event.stop_propagation();
|
event.stop_propagation();
|
||||||
let task = task_clone.clone();
|
let task = task.clone();
|
||||||
async move {
|
async move {
|
||||||
let completed_task = complete_task(task.id()).await;
|
let completed_task = complete_task(task.id()).await;
|
||||||
query_client.invalidate_queries(&[
|
let mut query_keys = vec![
|
||||||
QueryKey::Tasks,
|
QueryKey::Tasks,
|
||||||
QueryKey::TasksInCategory(
|
QueryKey::TasksInCategory(
|
||||||
completed_task.unwrap().category().clone()
|
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 {
|
div {
|
||||||
class: "flex flex-col",
|
class: "flex flex-row gap-3",
|
||||||
div {
|
if let Some(deadline) = task.deadline() {
|
||||||
class: "mt-1 grow font-medium",
|
div {
|
||||||
{task.title()}
|
class: "text-sm text-zinc-400",
|
||||||
},
|
i {
|
||||||
div {
|
class: "fa-solid fa-bomb"
|
||||||
class: "flex flex-row gap-3",
|
},
|
||||||
if let Some(deadline) = task.deadline() {
|
{deadline.format(" %m. %d.").to_string()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Category::Calendar { time, .. } = task.category() {
|
||||||
|
if let Some(calendar_time) = time {
|
||||||
div {
|
div {
|
||||||
class: "text-sm text-zinc-400",
|
class: "text-sm text-zinc-400",
|
||||||
i {
|
i {
|
||||||
class: "fa-solid fa-bomb"
|
class: "fa-solid fa-clock"
|
||||||
},
|
},
|
||||||
{deadline.format(" %m. %d.").to_string()}
|
{calendar_time.time().format(" %k:%M").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()}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user