This commit is contained in:
2026-01-28 16:19:43 +01:00
parent 472b33688d
commit 81858de6fc
27 changed files with 462 additions and 404 deletions

View File

@@ -15,6 +15,7 @@ input[type="range"]::-moz-range-thumb, input[type="range"]::-webkit-slider-thumb
filter: drop-shadow(0 var(--spacing) 0 var(--color-gray-500)); filter: drop-shadow(0 var(--spacing) 0 var(--color-gray-500));
border: 0; border: 0;
border-radius: 0.5rem; border-radius: 0.5rem;
cursor: pointer;
} }
input[type="range"]::-webkit-slider-thumb { input[type="range"]::-webkit-slider-thumb {

View File

@@ -1,4 +1,5 @@
use crate::internationalization::get_language_identifier; use crate::internationalization::get_language_identifier;
use crate::route::Route; use crate::route::Route;
use dioxus::core_macro::rsx; use dioxus::core_macro::rsx;
use dioxus::dioxus_core::Element; use dioxus::dioxus_core::Element;
@@ -41,7 +42,7 @@ pub(crate) fn App() -> Element {
document::Link { rel: "manifest", href: MANIFEST, crossorigin: "use-credentials" } document::Link { rel: "manifest", href: MANIFEST, crossorigin: "use-credentials" }
div { div {
class: "min-h-screen pt-4 pb-36 flex flex-col text-gray-300 bg-gray-900", class: "min-h-screen py-4 flex flex-col text-gray-300 bg-gray-900",
Router::<Route> {} Router::<Route> {}
} }
} }

View File

@@ -1,79 +1,23 @@
use crate::components::error_boundary_message::ErrorBoundaryMessage;
use crate::components::navigation::Navigation; use crate::components::navigation::Navigation;
use crate::components::project_form::ProjectForm;
use crate::components::task_form::TaskForm;
use crate::models::project::Project;
use crate::models::task::Task;
use crate::route::Route;
use dioxus::prelude::*; use dioxus::prelude::*;
#[component] #[component]
pub(crate) fn BottomPanel(display_form: Signal<bool>) -> Element { pub(crate) fn BottomPanel() -> Element {
// A signal for delaying the application of styles.
#[allow(clippy::redundant_closure)]
let mut expanded = use_signal(|| display_form());
let navigation_expanded = use_signal(|| false); let navigation_expanded = use_signal(|| false);
let current_route = use_route();
let mut project_being_edited = use_context::<Signal<Option<Project>>>();
let mut task_being_edited = use_context::<Signal<Option<Task>>>();
use_effect(use_reactive(&display_form, move |display_form| {
if display_form() {
expanded.set(true);
} else {
spawn(async move {
// Necessary for a smooth not instant height transition.
#[cfg(not(feature = "server"))]
async_std::task::sleep(std::time::Duration::from_millis(500)).await;
/* The check is necessary for the situation when the user expands the panel while
it is being closed. */
if !display_form() {
expanded.set(false);
}
});
}
}));
rsx! { rsx! {
div { div {
class: format!( class: format!(
"flex flex-col pointer-events-auto bg-gray-800 transition-[height] duration-[500ms] ease-[cubic-bezier(0.79,0.14,0.15,0.86)] overflow-y-scroll {}", "flex flex-col pointer-events-auto bg-gray-800 transition-[height] duration-[500ms] ease-[cubic-bezier(0.79,0.14,0.15,0.86)] overflow-y-scroll {}",
match (display_form(), current_route, navigation_expanded()) { if navigation_expanded() {
(false, _, false) => "h-[66px]", "h-[130px]"
(false, _, true) => "h-[130px]", } else {
(true, Route::ProjectsPage, _) => "h-[130px]", "h-[66px]"
(true, _, _) => "h-[506px]",
} }
), ),
if expanded() {
ErrorBoundaryMessage {
match current_route {
Route::ProjectsPage => rsx! {
ProjectForm {
project: project_being_edited(),
on_successful_submit: move |_| {
display_form.set(false);
project_being_edited.set(None);
}
}
},
_ => rsx! {
TaskForm {
task: task_being_edited(),
on_successful_submit: move |_| {
display_form.set(false);
task_being_edited.set(None);
}
}
}
}
}
} else {
Navigation { Navigation {
is_expanded: navigation_expanded, is_expanded: navigation_expanded,
} }
} }
} }
} }
}

View File

@@ -0,0 +1,29 @@
use dioxus::prelude::*;
#[component]
pub(crate) fn ButtonPrimary(
class: Option<String>,
children: Element,
#[props(extends = GlobalAttributes, extends = button)] attributes: Vec<Attribute>,
// TODO: Remove this once https://github.com/DioxusLabs/dioxus/issues/4019 gets resolved.
onclick: Option<Callback<Event<MouseData>>>,
) -> Element {
rsx! {
button {
class: format!(
"cursor-pointer pb-[6px] hover:pb-[7px] active:pb-[2px] mt-[1px] hover:mt-0 active:mt-[5px] hover:*:drop-shadow-[0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted)] active:*:drop-shadow-[0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted)] transition-all duration-150 {}",
class.unwrap_or("".to_owned())
),
onclick: move |event| {
if let Some(onclick) = onclick {
onclick.call(event);
}
},
..attributes,
div {
class: "py-3.5 px-4 flex flex-row justify-center items-center bg-amber-300-muted drop-shadow-[0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted)] text-amber-700-muted rounded-xl transition-all duration-150",
{children}
}
}
}
}

View File

@@ -0,0 +1,29 @@
use dioxus::prelude::*;
#[component]
pub(crate) fn ButtonSecondary(
class: Option<String>,
children: Element,
#[props(extends = GlobalAttributes, extends = button)] attributes: Vec<Attribute>,
// TODO: Remove this once https://github.com/DioxusLabs/dioxus/issues/4019 gets resolved.
onclick: Option<Callback<Event<MouseData>>>,
) -> Element {
rsx! {
button {
class: format!(
"cursor-pointer pb-[6px] hover:pb-[7px] active:pb-[2px] mt-[1px] hover:mt-0 active:mt-[5px] hover:*:drop-shadow-[0_7px_0_var(--color-gray-800)] active:*:drop-shadow-[0_2px_0_var(--color-gray-800)] transition-all duration-150 {}",
class.unwrap_or("".to_owned())
),
onclick: move |event| {
if let Some(onclick) = onclick {
onclick.call(event);
}
},
..attributes,
div {
class: "py-3.5 px-4 flex flex-row justify-center items-center bg-gray-600 drop-shadow-[0_6px_0_var(--color-gray-800)] rounded-xl transition-all duration-150",
{children}
}
}
}
}

View File

@@ -4,6 +4,7 @@ use dioxus::core_macro::rsx;
use dioxus::dioxus_core::Element; use dioxus::dioxus_core::Element;
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_free_icons::Icon; use dioxus_free_icons::Icon;
use dioxus_free_icons::icons::fa_regular_icons::FaLightbulb;
use dioxus_free_icons::icons::fa_solid_icons::{ use dioxus_free_icons::icons::fa_solid_icons::{
FaCalendarDays, FaForward, FaHourglassHalf, FaInbox, FaQuestion, FaSignsPost, FaWater FaCalendarDays, FaForward, FaHourglassHalf, FaInbox, FaQuestion, FaSignsPost, FaWater
}; };
@@ -17,7 +18,7 @@ pub(crate) fn CategoryInput(
div { div {
class: format!("grid grid-cols-3 gap-3 {}", class.unwrap_or("")), class: format!("grid grid-cols-3 gap-3 {}", class.unwrap_or("")),
SelectButton { SelectButton {
icon: FaQuestion, icon: FaLightbulb,
is_selected: matches!(selected_category(), Category::SomedayMaybe), is_selected: matches!(selected_category(), Category::SomedayMaybe),
on_select: move |_| { on_select: move |_| {
selected_category.set(Category::SomedayMaybe); selected_category.set(Category::SomedayMaybe);

View File

@@ -0,0 +1,29 @@
use crate::components::{button_primary::ButtonPrimary, task_form::TASK_BEING_EDITED};
use crate::route::Route;
use dioxus::prelude::*;
use dioxus_free_icons::{Icon, icons::fa_solid_icons::FaGavel};
#[component]
pub(crate) fn CreateButton() -> Element {
let navigator = use_navigator();
let current_route = use_route();
rsx! {
ButtonPrimary {
class: "pointer-events-auto m-4 self-end *:rounded-full! *:p-4",
onclick: move |_| {
*TASK_BEING_EDITED.write() = None;
navigator.push(
match current_route {
Route::ProjectsPage => Route::ProjectFormPage,
_ => Route::TaskFormPage,
}
);
},
Icon {
icon: FaGavel,
height: 24,
width: 24
}
}
}
}

View File

@@ -1,39 +0,0 @@
use crate::models::project::Project;
use crate::models::task::Task;
use dioxus::prelude::*;
use dioxus_free_icons::{
Icon,
icons::fa_solid_icons::{FaGavel, FaPlus, FaXmark},
};
#[component]
pub(crate) fn FormOpenButton(is_opened: Signal<bool>) -> Element {
let mut project_being_edited = use_context::<Signal<Option<Project>>>();
let mut task_being_edited = use_context::<Signal<Option<Task>>>();
rsx! {
button {
class: "pointer-events-auto m-4 p-4 self-end bg-amber-300-muted drop-shadow-[0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted)] rounded-full text-amber-700-muted cursor-pointer",
onclick: move |_| {
if is_opened() {
project_being_edited.set(None);
task_being_edited.set(None);
}
is_opened.set(!is_opened());
},
if is_opened() {
Icon {
icon: FaXmark,
height: 24,
width: 24
}
} else {
Icon {
icon: FaGavel,
height: 24,
width: 24
}
}
}
}
}

View File

@@ -2,41 +2,46 @@ use dioxus::prelude::*;
#[component] #[component]
pub(crate) fn Input( pub(crate) fn Input(
name: String,
required: Option<bool>,
disabled: Option<bool>,
initial_value: Option<String>,
class: Option<String>, class: Option<String>,
name: String,
r#type: String, r#type: String,
inputmode: Option<String>, id: Option<String>,
min: Option<String>, #[props(extends = GlobalAttributes, extends = input)] attributes: Vec<Attribute>,
// TODO: Remove this once https://github.com/DioxusLabs/dioxus/issues/5271 gets resolved.
autofocus: Option<bool>, autofocus: Option<bool>,
// TODO: Remove this once https://github.com/DioxusLabs/dioxus/issues/4019 gets resolved.
oninput: Option<Callback<Event<FormData>>>, oninput: Option<Callback<Event<FormData>>>,
onchange: Option<Callback<Event<FormData>>>
) -> Element { ) -> Element {
rsx! { rsx! {
input { input {
name: name.clone(),
required,
disabled,
initial_value,
class: format!( class: format!(
"pt-3 pb-2.25 {} grow bg-gray-800-muted drop-shadow-[0_calc(0px_-_var(--spacing))_0_var(--color-gray-900-muted)] rounded-xl {}", "pt-3 pb-2.25 {} bg-gray-800-muted enabled:hover:bg-gray-800 enabled:active:bg-gray-800 drop-shadow-[0_calc(0px_-_var(--spacing))_0_var(--color-gray-900-muted)] rounded-xl outline-0 {} transition-all duration-150 {}",
match r#type.as_str() { match r#type.as_str() {
"date" => "ps-3.25 pe-3", "date" => "ps-3.25 pe-3",
_ => "px-4" _ => "px-4"
}, },
match r#type.as_str() {
"text" | "number" => "",
_ => "enabled:cursor-pointer"
},
class.unwrap_or("".to_owned()) class.unwrap_or("".to_owned())
), ),
name: name.clone(),
r#type, r#type,
inputmode, id: id.unwrap_or(format!("input_{}", name)),
min,
autofocus, autofocus,
id: "input_{name}",
oninput: move |event| { oninput: move |event| {
if let Some(oninput) = oninput { if let Some(oninput) = oninput {
oninput.call(event); oninput.call(event);
} }
} },
onchange: move |event| {
if let Some(onchange) = oninput {
onchange.call(event);
}
},
..attributes
} }
} }
} }

View File

@@ -9,7 +9,7 @@ pub(crate) fn InputLabel<I: IconShape + Clone + PartialEq + 'static>(
rsx! { rsx! {
label { label {
r#for, r#for,
class: "mt-0.5 min-w-6 flex flex-row justify-center items-center", class: "mt-0.5 min-w-7 flex flex-row justify-center items-center",
Icon { Icon {
class: "text-gray-600", class: "text-gray-600",
icon, icon,

View File

@@ -1,10 +1,12 @@
pub(crate) mod app; pub(crate) mod app;
pub(crate) mod bottom_panel; pub(crate) mod bottom_panel;
pub(crate) mod button_primary;
pub(crate) mod button_secondary;
pub(crate) mod category_calendar_task_list; pub(crate) mod category_calendar_task_list;
pub(crate) mod category_input; pub(crate) mod category_input;
pub(crate) mod category_today_task_list; pub(crate) mod category_today_task_list;
pub(crate) mod error_boundary_message; pub(crate) mod error_boundary_message;
pub(crate) mod form_open_button; pub(crate) mod create_button;
pub(crate) mod input; pub(crate) mod input;
pub(crate) mod input_label; pub(crate) mod input_label;
pub(crate) mod navigation; pub(crate) mod navigation;

View File

@@ -2,9 +2,10 @@ use crate::components::navigation_item::NavigationItem;
use crate::route::Route; use crate::route::Route;
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_free_icons::Icon; use dioxus_free_icons::Icon;
use dioxus_free_icons::icons::fa_regular_icons::FaLightbulb;
use dioxus_free_icons::icons::fa_solid_icons::{ use dioxus_free_icons::icons::fa_solid_icons::{
FaBars, FaCalendarDay, FaCalendarDays, FaCheck, FaForward, FaHourglassHalf, FaInbox, FaList, FaBars, FaCalendarDay, FaCalendarDays, FaCheck, FaSignsPost, FaHourglassHalf, FaInbox, FaList,
FaQuestion, FaTrashCan, FaQuestion, FaTrashCan, FaVolcano,
}; };
#[component] #[component]
@@ -31,7 +32,7 @@ pub(crate) fn Navigation(is_expanded: Signal<bool>) -> Element {
}, },
NavigationItem { NavigationItem {
route: Route::CategoryNextStepsPage, route: Route::CategoryNextStepsPage,
icon: FaForward icon: FaSignsPost
}, },
NavigationItem { NavigationItem {
route: Route::CategoryCalendarPage, route: Route::CategoryCalendarPage,
@@ -57,11 +58,11 @@ pub(crate) fn Navigation(is_expanded: Signal<bool>) -> Element {
}, },
NavigationItem { NavigationItem {
route: Route::CategoryDonePage, route: Route::CategoryDonePage,
icon: FaCheck icon: FaVolcano
}, },
NavigationItem { NavigationItem {
route: Route::CategorySomedayMaybePage, route: Route::CategorySomedayMaybePage,
icon: FaQuestion icon: FaLightbulb
}, },
NavigationItem { NavigationItem {
route: Route::CategoryWaitingForPage, route: Route::CategoryWaitingForPage,

View File

@@ -13,7 +13,7 @@ pub(crate) fn NavigationItem<I: IconShape + Clone + PartialEq + 'static>(
Link { Link {
to: route.clone(), to: route.clone(),
class: format!( class: format!(
"py-2.5 flex flex-row justify-center items-center", "py-2.5 flex flex-row justify-center items-center hover:*:bg-gray-900 active:*:text-gray-400",
), ),
div { div {
class: format!("pt-2.5 px-4 {} transition-all duration-150", class: format!("pt-2.5 px-4 {} transition-all duration-150",

View File

@@ -1,24 +1,18 @@
use crate::{hooks::use_projects, models::project::Project}; use crate::{hooks::use_projects, models::project::Project};
use dioxus::prelude::*; use dioxus::prelude::*;
use crate::route::Route;
#[component] #[component]
pub(crate) fn ProjectList() -> Element { pub(crate) fn ProjectList() -> Element {
let projects = use_projects()?; let projects = use_projects()?;
let mut project_being_edited = use_context::<Signal<Option<Project>>>();
rsx! { rsx! {
div { div {
class: "flex flex-col", class: "flex flex-col",
for project in projects { for project in projects {
div { Link {
class: "px-7 py-4 select-none text-pretty wrap-anywhere",
to: Route::ProjectFormPage,
key: "{project.id}", key: "{project.id}",
class: format!(
"px-7 py-4 select-none {} text-pretty wrap-anywhere",
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.clone()} {project.title.clone()}
} }
} }

View File

@@ -10,8 +10,8 @@ pub(crate) fn ProjectSelect(initial_selected_id: Option<i32>) -> Element {
rsx! { rsx! {
select { select {
name: "project_id", name: "project_id",
class: "px-4 pt-3 pb-2.25 bg-gray-800-muted drop-shadow-[0_calc(0px_-_var(--spacing))_0_var(--color-gray-900-muted)] rounded-xl grow cursor-pointer", class: "px-4 pt-3 pb-2.25 bg-gray-800-muted enabled:hover:bg-gray-800 enabled:active:bg-gray-800 drop-shadow-[0_calc(0px_-_var(--spacing))_0_var(--color-gray-900-muted)] rounded-xl grow cursor-pointer",
id: "input_project", id: "input_project_id",
option { option {
value: 0, value: 0,
{t!("none")} {t!("none")}

View File

@@ -18,7 +18,7 @@ pub(crate) fn SelectButton<I: IconShape + Clone + PartialEq + 'static>(
class: format!( class: format!(
"pt-4.5 flex flex-row justify-center items-center {} rounded-xl transition-all duration-150", "pt-4.5 flex flex-row justify-center items-center {} rounded-xl transition-all duration-150",
if is_selected { "pb-3.75 bg-gray-900 drop-shadow-[0_0_0_var(--color-gray-900-muted)]" } if is_selected { "pb-3.75 bg-gray-900 drop-shadow-[0_0_0_var(--color-gray-900-muted)]" }
else { "pb-2.75 mt-1 bg-gray-800-muted drop-shadow-[0_calc(0px_-_var(--spacing))_0_var(--color-gray-900-muted)] text-gray-400 cursor-pointer" } else { "pb-2.75 mt-1 bg-gray-800-muted hover:bg-gray-800 drop-shadow-[0_calc(0px_-_var(--spacing))_0_var(--color-gray-900-muted)] text-gray-400 cursor-pointer" }
), ),
onclick: move |_| { onclick: move |_| {
on_select.call(()); on_select.call(());

View File

@@ -1,3 +1,6 @@
use crate::components::button_secondary::ButtonSecondary;
use crate::components::input::Input;
use crate::components::input_label::InputLabel;
use crate::hooks::use_subtasks_of_task; use crate::hooks::use_subtasks_of_task;
use crate::models::subtask::NewSubtask; use crate::models::subtask::NewSubtask;
use crate::models::task::Task; use crate::models::task::Task;
@@ -6,14 +9,15 @@ use dioxus::core_macro::{component, rsx};
use dioxus::dioxus_core::Element; use dioxus::dioxus_core::Element;
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_free_icons::Icon; use dioxus_free_icons::Icon;
use dioxus_free_icons::icons::fa_regular_icons::FaSquare; use dioxus_free_icons::icons::fa_solid_icons::{FaGavel, FaListCheck, FaTrashCan};
use dioxus_free_icons::icons::fa_solid_icons::{FaListCheck, FaPlus, FaSquareCheck, FaTrashCan};
#[component] #[component]
pub(crate) fn SubtasksForm(task: Task) -> Element { pub(crate) fn SubtasksForm(task: Task) -> Element {
let subtasks = use_subtasks_of_task(task.id)?; let subtasks = use_subtasks_of_task(task.id)?;
let mut new_title = use_signal(String::new); let mut new_title = use_signal(String::new);
rsx! { rsx! {
div {
class: "flex flex-col gap-3",
form { form {
class: "flex flex-row items-center gap-3", class: "flex flex-row items-center gap-3",
onsubmit: move |event| { onsubmit: move |event| {
@@ -22,7 +26,7 @@ pub(crate) fn SubtasksForm(task: Task) -> Element {
async move { async move {
let new_subtask = NewSubtask { let new_subtask = NewSubtask {
task_id: task.id, task_id: task.id,
title: event.get("title").first().cloned().and_then(|value| match value { title: event.get("new_title").first().cloned().and_then(|value| match value {
FormValue::Text(value) => Some(value), FormValue::Text(value) => Some(value),
FormValue::File(_) => None FormValue::File(_) => None
}).unwrap(), }).unwrap(),
@@ -32,32 +36,24 @@ pub(crate) fn SubtasksForm(task: Task) -> Element {
new_title.set(String::new()); new_title.set(String::new());
} }
}, },
label { InputLabel {
r#for: "input_new_title",
class: "min-w-6 flex flex-row justify-center items-center",
Icon {
class: "text-zinc-400/50",
icon: FaListCheck, icon: FaListCheck,
height: 16, r#for: "input_new_title"
width: 16
}
} }
div { div {
class: "grow grid grid-cols-6 gap-2", class: "grow flex flex-row items-end gap-3",
input { Input {
name: "title", class: "grow",
name: "new_title",
r#type: "text",
required: true, required: true,
value: new_title, value: new_title,
r#type: "text", onchange: move |event: Event<FormData>| new_title.set(event.value())
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 { ButtonSecondary {
r#type: "submit", r#type: "submit",
class: "py-2 col-span-1 flex flex-row justify-center items-center bg-zinc-800/50 rounded-lg cursor-pointer",
Icon { Icon {
icon: FaPlus, icon: FaGavel,
height: 16, height: 16,
width: 16 width: 16
} }
@@ -69,7 +65,7 @@ pub(crate) fn SubtasksForm(task: Task) -> Element {
key: "{subtask.id}", key: "{subtask.id}",
class: "flex flex-row items-center gap-3", class: "flex flex-row items-center gap-3",
button { button {
class: "min-w-6 flex flex-row justify-center items-center text-zinc-400/50 cursor-pointer", class: "mt-1.5 hover:mt-1 hover:pb-0.5 min-w-7 cursor-pointer transition-all duration-150",
onclick: { onclick: {
let subtask = subtask.clone(); let subtask = subtask.clone();
move |_| { move |_| {
@@ -87,26 +83,18 @@ pub(crate) fn SubtasksForm(task: Task) -> Element {
} }
} }
}, },
if subtask.is_completed { div {
Icon { class: format!("grow h-7 w-7 mb-[4px] drop-shadow-[0_1px_0_var(--color-gray-800),0_1px_0_var(--color-gray-800),0_1px_0_var(--color-gray-800),0_1px_0_var(--color-gray-800)] rounded-full {}",
icon: FaSquareCheck, if subtask.is_completed {"bg-gray-600"} else {"border-3 border-gray-600"}
height: 24, )
width: 24
}
} else {
Icon {
icon: FaSquare,
height: 24,
width: 24
}
} }
} }
div { div {
class: "grow grid grid-cols-6 gap-2", class: "grow flex flex-row items-end gap-3",
input { Input {
class: "grow",
name: "title_edit_{subtask.id}",
r#type: "text", r#type: "text",
class: "grow py-2 px-3 col-span-5 bg-zinc-800/50 rounded-lg",
id: "input_title_{subtask.id}",
initial_value: subtask.title.clone(), initial_value: subtask.title.clone(),
onchange: { onchange: {
let subtask = subtask.clone(); let subtask = subtask.clone();
@@ -130,9 +118,8 @@ pub(crate) fn SubtasksForm(task: Task) -> Element {
} }
} }
} }
button { ButtonSecondary {
r#type: "button", r#type: "button",
class: "py-2 flex flex-row justify-center items-center col-span-1 bg-zinc-800/50 rounded-lg cursor-pointer",
onclick: { onclick: {
let subtask = subtask.clone(); let subtask = subtask.clone();
move |_| { move |_| {
@@ -153,3 +140,4 @@ pub(crate) fn SubtasksForm(task: Task) -> Element {
} }
} }
} }
}

View File

@@ -1,3 +1,5 @@
use crate::components::button_primary::ButtonPrimary;
use crate::components::button_secondary::ButtonSecondary;
use crate::components::category_input::CategoryInput; use crate::components::category_input::CategoryInput;
use crate::components::input::Input; use crate::components::input::Input;
use crate::components::input_label::InputLabel; use crate::components::input_label::InputLabel;
@@ -5,8 +7,8 @@ use crate::components::project_select::ProjectSelect;
use crate::components::reoccurrence_interval_input::ReoccurrenceIntervalInput; use crate::components::reoccurrence_interval_input::ReoccurrenceIntervalInput;
use crate::components::subtasks_form::SubtasksForm; use crate::components::subtasks_form::SubtasksForm;
use crate::models::category::{CalendarTime, Category, Reoccurrence}; use crate::models::category::{CalendarTime, Category, Reoccurrence};
use crate::models::task::NewTask;
use crate::models::task::Task; use crate::models::task::Task;
use crate::models::task::{NewTask, TaskWithSubtasks};
use crate::route::Route; use crate::route::Route;
use crate::server::tasks::{create_task, delete_task, edit_task}; use crate::server::tasks::{create_task, delete_task, edit_task};
use chrono::Duration; use chrono::Duration;
@@ -16,7 +18,7 @@ use dioxus::prelude::*;
use dioxus_free_icons::Icon; use dioxus_free_icons::Icon;
use dioxus_free_icons::icons::fa_solid_icons::{ use dioxus_free_icons::icons::fa_solid_icons::{
FaBell, FaBomb, FaClock, FaFeatherPointed, FaFloppyDisk, FaHourglassEnd, FaLayerGroup, FaList, FaBell, FaBomb, FaClock, FaFeatherPointed, FaFloppyDisk, FaHourglassEnd, FaLayerGroup, FaList,
FaPenClip, FaRepeat, FaScroll, FaTrashCan, FaVolcano, FaXmark, FaPenClip, FaRepeat, FaScroll, FaStamp, FaTrashCan, FaVolcano, FaXmark,
}; };
use dioxus_i18n::t; use dioxus_i18n::t;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -53,24 +55,19 @@ struct InputData {
project_id: Option<String>, project_id: Option<String>,
} }
pub(crate) static TASK_BEING_EDITED: GlobalSignal<Option<Task>> = Signal::global(|| None);
pub(crate) static LATEST_VISITED_CATEGORY: GlobalSignal<Category> =
Signal::global(|| Category::Inbox);
#[component] #[component]
pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()>) -> Element { pub(crate) fn TaskForm() -> Element {
let route = use_route::<Route>(); let navigator = use_navigator();
let task = TASK_BEING_EDITED();
let selected_category = use_signal(|| { let selected_category = use_signal(|| {
if let Some(task) = &task { if let Some(task) = &task {
task.category.clone() task.category.clone()
} else { } else {
match route { LATEST_VISITED_CATEGORY()
Route::CategorySomedayMaybePage => Category::SomedayMaybe,
Route::CategoryWaitingForPage => Category::WaitingFor(String::new()),
Route::CategoryNextStepsPage => Category::NextSteps,
Route::CategoryCalendarPage | Route::CategoryTodayPage => Category::Calendar {
date: chrono::Local::now().date_naive(),
reoccurrence: None,
time: None,
},
_ => Category::Inbox,
}
} }
}); });
let category_calendar_reoccurrence_interval = use_signal(|| { let category_calendar_reoccurrence_interval = use_signal(|| {
@@ -110,7 +107,7 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()
rsx! { rsx! {
div { div {
class: "p-4 flex flex-col gap-10", class: "grow px-4 flex flex-col gap-6.5",
form { form {
class: "flex flex-col gap-8", class: "flex flex-col gap-8",
id: "form_task", id: "form_task",
@@ -154,12 +151,14 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()
project_id: input_data.project_id project_id: input_data.project_id
.and_then(|deadline| deadline.parse().ok()).filter(|&id| id > 0), .and_then(|deadline| deadline.parse().ok()).filter(|&id| id > 0),
}; };
if let Some(task) = task { let result = if let Some(task) = task {
let _ = edit_task(task.id, new_task).await; edit_task(task.id, new_task).await
} else { } else {
let _ = create_task(new_task).await; create_task(new_task).await
};
if result.is_ok() {
navigator.go_back();
} }
on_successful_submit.call(());
} }
}, },
div { div {
@@ -169,6 +168,7 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()
icon: FaFeatherPointed icon: FaFeatherPointed
}, },
Input { Input {
class: "grow",
name: "title", name: "title",
required: true, required: true,
initial_value: task.as_ref().map(|task| task.title.clone()), initial_value: task.as_ref().map(|task| task.title.clone()),
@@ -179,17 +179,16 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()
div { div {
class: "flex flex-row items-center gap-3", class: "flex flex-row items-center gap-3",
InputLabel { InputLabel {
r#for: "input_project", r#for: "input_project_id",
icon: FaList icon: FaList
}, },
SuspenseBoundary { SuspenseBoundary {
fallback: |_| { fallback: |_| {
rsx ! { rsx ! {
select { select {
class: "px-4.5 pt-3.5 pb-2.75 bg-gray-800-muted drop-shadow-[0_calc(0px_-_var(--spacing))_0_var(--color-gray-900-muted)] rounded-xl grow cursor-pointer", class: "px-4 pt-3 pb-2.25 bg-gray-800-muted drop-shadow-[0_calc(0px_-_var(--spacing))_0_var(--color-gray-900-muted)] rounded-xl grow cursor-pointer",
option { option {
value: 0, value: 0
{t!("none")}
} }
} }
} }
@@ -202,25 +201,25 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()
div { div {
class: "flex flex-row items-center gap-3", class: "flex flex-row items-center gap-3",
InputLabel { InputLabel {
r#for: "input_deadline", icon: FaBomb,
icon: FaBomb r#for: "input_deadline"
}, },
Input { Input {
name: "deadline", name: "deadline",
initial_value: task.as_ref().and_then(|task| task.deadline) initial_value: task.as_ref().and_then(|task| task.deadline)
.map(|deadline| deadline.format("%Y-%m-%d").to_string()), .map(|deadline| deadline.format("%Y-%m-%d").to_string()),
r#type: "date", r#type: "date",
class: "grow basis-0 cursor-pointer" class: "grow basis-0"
} }
}, },
div { div {
class: "flex flex-row items-center gap-3", class: "flex flex-row items-center gap-3",
InputLabel { InputLabel {
icon: FaScroll, icon: FaScroll
}, },
CategoryInput { CategoryInput {
selected_category: selected_category, class: "grow",
class: "grow" selected_category: selected_category
} }
} }
match selected_category() { match selected_category() {
@@ -228,10 +227,11 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()
div { div {
class: "flex flex-row items-center gap-3", class: "flex flex-row items-center gap-3",
InputLabel { InputLabel {
r#for: "input_category_waiting_for",
icon: FaHourglassEnd, icon: FaHourglassEnd,
r#for: "input_category_waiting_for",
}, },
Input { Input {
class: "grow",
name: "category_waiting_for", name: "category_waiting_for",
required: true, required: true,
initial_value: waiting_for, initial_value: waiting_for,
@@ -243,20 +243,22 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()
div { div {
class: "flex flex-row items-center gap-3", class: "flex flex-row items-center gap-3",
InputLabel { InputLabel {
r#for: "input_category_calendar_date", icon: FaClock,
icon: FaClock r#for: "input_category_calendar_date"
}, },
div { div {
class: "grow flex flex-row gap-3", class: "grow flex flex-row gap-3",
Input { Input {
r#type: "date", class: "grow",
name: "category_calendar_date", name: "category_calendar_date",
r#type: "date",
required: true, required: true,
initial_value: date.format("%Y-%m-%d").to_string(), initial_value: date.format("%Y-%m-%d").to_string(),
}, },
Input { Input {
r#type: "time", class: "grow",
name: "category_calendar_time", name: "category_calendar_time",
r#type: "time",
initial_value: time.map(|calendar_time| initial_value: time.map(|calendar_time|
calendar_time.time.format("%H:%M").to_string() calendar_time.time.format("%H:%M").to_string()
), ),
@@ -269,8 +271,8 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()
div { div {
class: "flex flex-row items-center gap-3", class: "flex flex-row items-center gap-3",
InputLabel { InputLabel {
r#for: "category_calendar_reoccurrence_length", icon: FaRepeat,
icon: FaRepeat r#for: "category_calendar_reoccurrence_length"
}, },
div { div {
class: "grow grid grid-cols-5 items-end gap-3", class: "grow grid grid-cols-5 items-end gap-3",
@@ -278,6 +280,7 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()
reoccurrence_interval: category_calendar_reoccurrence_interval reoccurrence_interval: category_calendar_reoccurrence_interval
}, },
Input { Input {
class: "text-right",
r#type: "number", r#type: "number",
inputmode: "numeric", inputmode: "numeric",
name: "category_calendar_reoccurrence_length", name: "category_calendar_reoccurrence_length",
@@ -288,7 +291,7 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()
String::new(), String::new(),
|_| reoccurrence.map_or(1, |reoccurrence| |_| reoccurrence.map_or(1, |reoccurrence|
reoccurrence.length).to_string() reoccurrence.length).to_string()
), )
} }
} }
}, },
@@ -296,18 +299,17 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()
div { div {
class: "flex flex-row items-center gap-3", class: "flex flex-row items-center gap-3",
InputLabel { InputLabel {
r#for: "category_calendar_reminder_offset_index", r#for: "input_category_calendar_reminder_offset_index",
icon: FaBell icon: FaBell
}, },
input { input {
r#type: "range", class: "grow",
name: "category_calendar_reminder_offset_index", name: "category_calendar_reminder_offset_index",
r#type: "range",
min: 0, min: 0,
max: REMINDER_OFFSETS.len() as i64 - 1, max: REMINDER_OFFSETS.len() as i64 - 1,
initial_value: category_calendar_reminder_offset_index() initial_value: category_calendar_reminder_offset_index()
.to_string(), .to_string(),
class: "grow cursor-pointer",
id: "category_calendar_has_reminder",
oninput: move |event| { oninput: move |event| {
category_calendar_reminder_offset_index.set( category_calendar_reminder_offset_index.set(
event.value().parse().unwrap() event.value().parse().unwrap()
@@ -315,8 +317,8 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()
} }
}, },
label { label {
r#for: "category_calendar_reminder_offset_index",
class: "pr-3 min-w-16 text-right", class: "pr-3 min-w-16 text-right",
r#for: "category_calendar_reminder_offset_index",
{REMINDER_OFFSETS[category_calendar_reminder_offset_index()] {REMINDER_OFFSETS[category_calendar_reminder_offset_index()]
.map( .map(
|offset| if offset.num_hours() < 1 { |offset| if offset.num_hours() < 1 {
@@ -342,17 +344,20 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()
} }
} }
} }
}
div { div {
class: "flex flex-row gap-3 mt-auto", class: "px-4 grid grid-cols-3 gap-3 mt-auto",
button { ButtonSecondary {
r#type: "button", r#type: "button",
class: "grow py-3 px-4 flex flex-row justify-center items-center bg-gray-600 drop-shadow-[0_6px_0_var(--color-gray-800)] rounded-xl cursor-pointer", class: "grow",
onclick: move |_| { onclick: {
let task = task.clone();
move |_| {
let task = task.clone(); let task = task.clone();
async move { async move {
if let Some(task) = task { if let Some(task) = task {
if let Category::Trash = task.category { let result = if let Category::Trash = task.category {
let _ = delete_task(task.id).await; delete_task(task.id).await
} else { } else {
let new_task = NewTask { let new_task = NewTask {
title: task.title.to_owned(), title: task.title.to_owned(),
@@ -360,34 +365,51 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()
category: Category::Trash, category: Category::Trash,
project_id: task.project_id project_id: task.project_id
}; };
let _ = edit_task(task.id, new_task).await; edit_task(task.id, new_task).await.map(|_| ())
};
if result.is_ok() {
/* TODO: Might not work on mobile due to
https://dioxuslabs.com/learn/0.7/essentials/router/navigation#history-buttons.
*/
navigator.go_back();
}
} else {
navigator.go_back();
} }
} }
on_successful_submit.call(());
} }
}, },
Icon { Icon {
icon: FaVolcano, icon: FaTrashCan,
height: 16, height: 16,
width: 16 width: 16
} }
} }
button { if task.is_some() {
div {
class: "grow flex flex-col items-stretch",
GoBackButton {
ButtonSecondary {
/* TODO: Replace w-full` with proper flexbox styling once
https://github.com/DioxusLabs/dioxus/issues/5269 is solved. */
class: "w-full",
r#type: "button", r#type: "button",
class: "grow py-3 px-4 flex flex-row justify-center items-center bg-gray-600 drop-shadow-[0_6px_0_var(--color-gray-800)] rounded-xl cursor-pointer",
onclick: move |_| {},
Icon { Icon {
icon: FaXmark, icon: FaXmark,
height: 16, height: 16,
width: 16 width: 16
} }
} }
button { }
}
} else {
div {}
}
ButtonPrimary {
form: "form_task", form: "form_task",
r#type: "submit", r#type: "submit",
class: "grow py-3.5 px-4 flex flex-row justify-center items-center bg-amber-300-muted drop-shadow-[0_6px_0_var(--color-amber-700-muted)] text-amber-700-muted rounded-xl cursor-pointer",
Icon { Icon {
icon: FaFloppyDisk, icon: FaStamp,
height: 16, height: 16,
width: 16 width: 16
} }
@@ -395,4 +417,3 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()
} }
} }
} }
}

View File

@@ -1,17 +1,16 @@
use crate::components::task_form::TASK_BEING_EDITED;
use crate::components::task_list_item::TaskListItem; use crate::components::task_list_item::TaskListItem;
use crate::models::category::Category; use crate::models::category::Category;
use crate::models::task::{Task, TaskWithSubtasks}; use crate::models::task::TaskWithSubtasks;
use crate::route::Route;
use crate::server::tasks::complete_task; use crate::server::tasks::complete_task;
use dioxus::core_macro::rsx; use dioxus::core_macro::rsx;
use dioxus::dioxus_core::Element; use dioxus::dioxus_core::Element;
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_free_icons::Icon;
use dioxus_free_icons::icons::fa_regular_icons::{FaCircle, FaSquare};
use dioxus_free_icons::icons::fa_solid_icons::FaSquareCheck;
#[component] #[component]
pub(crate) fn TaskList(tasks: Vec<TaskWithSubtasks>, class: Option<&'static str>) -> Element { pub(crate) fn TaskList(tasks: Vec<TaskWithSubtasks>, class: Option<&'static str>) -> Element {
let mut task_being_edited = use_context::<Signal<Option<Task>>>(); let navigator = use_navigator();
rsx! { rsx! {
div { div {
class: format!("flex flex-col {}", class.unwrap_or("")), class: format!("flex flex-col {}", class.unwrap_or("")),
@@ -19,27 +18,32 @@ pub(crate) fn TaskList(tasks: Vec<TaskWithSubtasks>, class: Option<&'static str>
div { div {
key: "{task.task.id}", key: "{task.task.id}",
class: format!( class: format!(
"px-7 pt-4.25 {} flex flex-row items-start gap-4 select-none {}", "px-7 pt-3.75 {} flex flex-row items-start gap-4 hover:bg-gray-800 cursor-pointer select-none transition-all duration-150",
if task.task.deadline.is_some() || !task.subtasks.is_empty() { if task.task.deadline.is_some() || !task.subtasks.is_empty() {
"pb-0.25" "pb-0.25"
} else if let Category::Calendar { time, .. } = &task.task.category { } else if let Category::Calendar { time, .. } = &task.task.category {
if time.is_some() { if time.is_some() {
"pb-0.25" "pb-0.25"
} else { } else {
"pb-4.25" "pb-3.75"
} }
} else { } else {
"pb-4.25" "pb-3.75"
}, },
if task_being_edited().is_some_and(|t| t.id == task.task.id) {
"bg-zinc-700"
} else { "" }
), ),
onclick: { onclick: {
let task = task.clone(); let task = task.clone();
move |_| task_being_edited.set(Some(task.task.clone())) move |_| {
*TASK_BEING_EDITED.write() = Some(task.task.clone());
navigator.push(Route::TaskFormPage);
}
}, },
button { button {
class: format!(
"mt-0.5 hover:mt-0 hover:pb-0.5 transition-all duration-150 cursor-pointer {}",
if let Category::Done = task.task.category { "pointer-events-none" }
else { "" }
),
onclick: { onclick: {
move |event: Event<MouseData>| { move |event: Event<MouseData>| {
// To prevent editing the task. // To prevent editing the task.
@@ -49,20 +53,18 @@ pub(crate) fn TaskList(tasks: Vec<TaskWithSubtasks>, class: Option<&'static str>
} }
} }
}, },
if let Category::Done = task.task.category {
Icon {
icon: FaSquareCheck,
height: 30,
width: 30
}
} else {
div { div {
class: "h-8 w-8 border-3 border-amber-300-muted drop-shadow-[0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted)] rounded-full cursor-pointer" class: format!("h-8 w-8 rounded-full {}",
if let Category::Done = task.task.category {
"mt-[3px] mb-[2px] bg-amber-300-muted"
} else {
"mb-[5px] border-3 border-amber-300-muted drop-shadow-[0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted)]"
} }
)
} }
}, },
div { div {
class: "mt-1", class: "mt-1.5",
TaskListItem { TaskListItem {
task: task.clone() task: task.clone()
} }

View File

@@ -1,2 +1,2 @@
mod main; pub(crate) mod suspense;
pub(crate) use main::Main; pub(crate) mod navigation;

38
src/layouts/navigation.rs Normal file
View File

@@ -0,0 +1,38 @@
use crate::components::bottom_panel::BottomPanel;
use crate::components::create_button::CreateButton;
use crate::components::sticky_bottom::StickyBottom;
use crate::components::task_form::LATEST_VISITED_CATEGORY;
use crate::models::category::Category;
use crate::route::Route;
use dioxus::core_macro::rsx;
use dioxus::dioxus_core::Element;
use dioxus::prelude::*;
#[component]
pub(crate) fn Navigation() -> Element {
let current_route = use_route();
use_effect(use_reactive(&current_route, move |current_route| {
*LATEST_VISITED_CATEGORY.write() = match current_route {
Route::CategorySomedayMaybePage => Category::SomedayMaybe,
Route::CategoryWaitingForPage => Category::WaitingFor(String::new()),
Route::CategoryNextStepsPage => Category::NextSteps,
Route::CategoryCalendarPage | Route::CategoryTodayPage => Category::Calendar {
date: chrono::Local::now().date_naive(),
reoccurrence: None,
time: None,
},
_ => Category::Inbox,
};
}));
rsx! {
div {
class: "pb-36",
Outlet::<Route> {}
}
StickyBottom {
CreateButton {},
BottomPanel {}
}
}
}

View File

@@ -1,5 +1,5 @@
use crate::components::bottom_panel::BottomPanel; use crate::components::bottom_panel::BottomPanel;
use crate::components::form_open_button::FormOpenButton; use crate::components::create_button::CreateButton;
use crate::components::sticky_bottom::StickyBottom; use crate::components::sticky_bottom::StickyBottom;
use crate::models::project::Project; use crate::models::project::Project;
use crate::models::task::Task; use crate::models::task::Task;
@@ -11,16 +11,7 @@ use dioxus_free_icons::Icon;
use dioxus_free_icons::icons::fa_solid_icons::FaCog; use dioxus_free_icons::icons::fa_solid_icons::FaCog;
#[component] #[component]
pub(crate) fn Main() -> Element { pub(crate) fn Suspense() -> Element {
let mut display_form = use_signal(|| false);
let project_being_edited =
use_context_provider::<Signal<Option<Project>>>(|| Signal::new(None));
let task_being_edited = use_context_provider::<Signal<Option<Task>>>(|| Signal::new(None));
use_effect(move || {
display_form.set(project_being_edited().is_some() || task_being_edited().is_some());
});
rsx! { rsx! {
SuspenseBoundary { SuspenseBoundary {
fallback: |_| { fallback: |_| {
@@ -38,13 +29,5 @@ pub(crate) fn Main() -> Element {
}, },
Outlet::<Route> {} Outlet::<Route> {}
} }
StickyBottom {
FormOpenButton {
is_opened: display_form,
}
BottomPanel {
display_form: display_form,
}
}
} }
} }

View File

@@ -7,6 +7,8 @@ use crate::views::category_someday_maybe_page::CategorySomedayMaybePage;
use crate::views::category_today_page::CategoryTodayPage; use crate::views::category_today_page::CategoryTodayPage;
use crate::views::category_trash_page::CategoryTrashPage; use crate::views::category_trash_page::CategoryTrashPage;
use crate::views::category_waiting_for_page::CategoryWaitingForPage; use crate::views::category_waiting_for_page::CategoryWaitingForPage;
use crate::views::project_form_page::ProjectFormPage;
use crate::views::task_form_page::TaskFormPage;
use crate::views::not_found_page::NotFoundPage; use crate::views::not_found_page::NotFoundPage;
use crate::views::projects_page::ProjectsPage; use crate::views::projects_page::ProjectsPage;
use dioxus::prelude::*; use dioxus::prelude::*;
@@ -16,8 +18,8 @@ use dioxus::prelude::*;
#[derive(Clone, Routable, Debug, PartialEq)] #[derive(Clone, Routable, Debug, PartialEq)]
#[rustfmt::skip] #[rustfmt::skip]
pub(crate) enum Route { pub(crate) enum Route {
#[layout(layouts::Main)] #[layout(layouts::navigation::Navigation)]
#[redirect("/", || Route::CategoryTodayPage {})] #[layout(layouts::suspense::Suspense)]
#[route("/today")] #[route("/today")]
CategoryTodayPage, CategoryTodayPage,
#[route("/inbox")] #[route("/inbox")]
@@ -37,6 +39,13 @@ pub(crate) enum Route {
#[route("/projects")] #[route("/projects")]
ProjectsPage, ProjectsPage,
#[end_layout] #[end_layout]
#[end_layout]
#[layout(layouts::suspense::Suspense)]
#[route("/task")]
TaskFormPage,
#[route("/project")]
ProjectFormPage,
#[end_layout]
#[redirect("/", || Route::CategoryTodayPage)] #[redirect("/", || Route::CategoryTodayPage)]
#[route("/:..route")] #[route("/:..route")]
NotFoundPage { NotFoundPage {

View File

@@ -1,5 +1,4 @@
use crate::components::error_boundary_message::ErrorBoundaryMessage; use crate::components::error_boundary_message::ErrorBoundaryMessage;
use crate::components::task_form::TaskForm;
use crate::models::category::Category; use crate::models::category::Category;
use crate::views::category_page::CategoryPage; use crate::views::category_page::CategoryPage;
use dioxus::core_macro::rsx; use dioxus::core_macro::rsx;
@@ -13,10 +12,6 @@ pub(crate) fn CategoryInboxPage() -> Element {
CategoryPage { CategoryPage {
category: Category::Inbox, category: Category::Inbox,
} }
TaskForm {
task: None,
on_successful_submit: move |_| {}
}
} }
} }
} }

View File

@@ -7,5 +7,7 @@ pub(crate) mod category_someday_maybe_page;
pub(crate) mod category_today_page; pub(crate) mod category_today_page;
pub(crate) mod category_trash_page; pub(crate) mod category_trash_page;
pub(crate) mod category_waiting_for_page; pub(crate) mod category_waiting_for_page;
pub(crate) mod project_form_page;
pub(crate) mod task_form_page;
pub(crate) mod not_found_page; pub(crate) mod not_found_page;
pub(crate) mod projects_page; pub(crate) mod projects_page;

View File

@@ -0,0 +1,11 @@
use crate::components::{error_boundary_message::ErrorBoundaryMessage, project_list::ProjectList};
use dioxus::prelude::*;
#[component]
pub(crate) fn ProjectFormPage() -> Element {
rsx! {
ErrorBoundaryMessage {
ProjectList {}
}
}
}

View File

@@ -0,0 +1,12 @@
use crate::components::{error_boundary_message::ErrorBoundaryMessage, task_form::TaskForm};
use dioxus::prelude::*;
#[component]
pub(crate) fn TaskFormPage() -> Element {
rsx! {
ErrorBoundaryMessage {
class: "grow py-4 flex flex-col",
TaskForm {}
}
}
}