feat: ability to view tasks in different categories
This commit is contained in:
parent
1233325f0b
commit
d7c56b7d92
@ -7,7 +7,7 @@ use dioxus::prelude::*;
|
|||||||
pub(crate) fn App() -> Element {
|
pub(crate) fn App() -> Element {
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
class: "min-h-screen text-white bg-neutral-800",
|
class: "min-h-screen text-zinc-200 bg-zinc-800",
|
||||||
Router::<Route> {}
|
Router::<Route> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
44
src/components/bottom_panel.rs
Normal file
44
src/components/bottom_panel.rs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
use std::thread::sleep;
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use crate::components::navigation::Navigation;
|
||||||
|
use crate::components::task_form::TaskForm;
|
||||||
|
use crate::components::task_list::TaskList;
|
||||||
|
use crate::models::category::Category;
|
||||||
|
use crate::route::Route;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub(crate) fn BottomPanel(creating_task: bool) -> Element {
|
||||||
|
let mut expanded = use_signal(|| creating_task);
|
||||||
|
let navigation_expanded = use_signal(|| false);
|
||||||
|
|
||||||
|
use_effect(use_reactive(&creating_task, move |creating_task| {
|
||||||
|
if creating_task {
|
||||||
|
expanded.set(true);
|
||||||
|
} else {
|
||||||
|
spawn(async move {
|
||||||
|
async_std::task::sleep(std::time::Duration::from_millis(500)).await;
|
||||||
|
expanded.set(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
class: format!(
|
||||||
|
"bg-zinc-700/50 rounded-t-xl border-t-zinc-600 border-t backdrop-blur drop-shadow-[0_-5px_10px_rgba(0,0,0,0.2)] transition-[height] duration-[500ms] ease-[cubic-bezier(0.79,0.14,0.15,0.86)] {}",
|
||||||
|
match (creating_task, navigation_expanded()) {
|
||||||
|
(false, false) => "h-[64px]",
|
||||||
|
(false, true) => "h-[128px]",
|
||||||
|
(true, _) => "h-[448px]",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
if expanded() {
|
||||||
|
TaskForm {}
|
||||||
|
} else {
|
||||||
|
Navigation {
|
||||||
|
expanded: navigation_expanded,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
104
src/components/category_input.rs
Normal file
104
src/components/category_input.rs
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
use crate::models::category::Category;
|
||||||
|
use crate::server::tasks::get_tasks_in_category;
|
||||||
|
use chrono::NaiveDate;
|
||||||
|
use dioxus::core_macro::rsx;
|
||||||
|
use dioxus::dioxus_core::Element;
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use std::fmt::format;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub(crate) fn CategoryInput(selected_category: Signal<Category>, class: Option<&'static str>) -> Element {
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
class: format!("flex flex-row gap-2 {}", class.unwrap_or("")),
|
||||||
|
button {
|
||||||
|
r#type: "button",
|
||||||
|
class: format!(
|
||||||
|
"py-2 rounded-lg grow basis-0 {}",
|
||||||
|
if selected_category() == Category::SomedayMaybe { "bg-zinc-500/50" }
|
||||||
|
else { "bg-zinc-800/50" }
|
||||||
|
),
|
||||||
|
onclick: move |_| {
|
||||||
|
selected_category.set(Category::SomedayMaybe);
|
||||||
|
},
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-question"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
button {
|
||||||
|
r#type: "button",
|
||||||
|
class: format!(
|
||||||
|
"py-2 rounded-lg grow basis-0 {}",
|
||||||
|
if selected_category() == Category::LongTerm { "bg-zinc-500/50" }
|
||||||
|
else { "bg-zinc-800/50" }
|
||||||
|
),
|
||||||
|
onclick: move |_| {
|
||||||
|
selected_category.set(Category::LongTerm);
|
||||||
|
},
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-water"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
button {
|
||||||
|
r#type: "button",
|
||||||
|
class: format!(
|
||||||
|
"py-2 rounded-lg grow basis-0 {}",
|
||||||
|
if let Category::WaitingFor(_) = selected_category() { "bg-zinc-500/50" }
|
||||||
|
else { "bg-zinc-800/50" }
|
||||||
|
),
|
||||||
|
onclick: move |_| {
|
||||||
|
selected_category.set(Category::WaitingFor(String::new()));
|
||||||
|
},
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-hourglass-half"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
button {
|
||||||
|
r#type: "button",
|
||||||
|
class: format!(
|
||||||
|
"py-2 rounded-lg grow basis-0 {}",
|
||||||
|
if selected_category() == Category::NextSteps { "bg-zinc-500/50" }
|
||||||
|
else { "bg-zinc-800/50" }
|
||||||
|
),
|
||||||
|
onclick: move |_| {
|
||||||
|
selected_category.set(Category::NextSteps);
|
||||||
|
},
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-forward"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
button {
|
||||||
|
r#type: "button",
|
||||||
|
class: format!(
|
||||||
|
"py-2 rounded-lg grow basis-0 {}",
|
||||||
|
if let Category::Calendar { .. } = selected_category() { "bg-zinc-500/50" }
|
||||||
|
else { "bg-zinc-800/50" }
|
||||||
|
),
|
||||||
|
onclick: move |_| {
|
||||||
|
selected_category.set(Category::Calendar {
|
||||||
|
date: NaiveDate::default(),
|
||||||
|
reoccurrence: None,
|
||||||
|
time: None,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-calendar-days"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
button {
|
||||||
|
r#type: "button",
|
||||||
|
class: format!(
|
||||||
|
"py-2 rounded-lg grow basis-0 {}",
|
||||||
|
if selected_category() == Category::Inbox { "bg-zinc-500/50" }
|
||||||
|
else { "bg-zinc-800/50" }
|
||||||
|
),
|
||||||
|
onclick: move |_| {
|
||||||
|
selected_category.set(Category::Inbox);
|
||||||
|
},
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-inbox"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
src/components/create_task_button.rs
Normal file
19
src/components/create_task_button.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
use dioxus::prelude::*;
|
||||||
|
use crate::components::task_list::TaskList;
|
||||||
|
use crate::models::category::Category;
|
||||||
|
use crate::route::Route;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub(crate) fn CreateTaskButton(creating: Signal<bool>) -> Element {
|
||||||
|
rsx! {
|
||||||
|
button {
|
||||||
|
class: "m-4 py-3 px-5 self-end text-center bg-zinc-300/50 rounded-xl border-t-zinc-200 border-t backdrop-blur drop-shadow-[0_-5px_10px_rgba(0,0,0,0.2)] text-2xl text-zinc-200",
|
||||||
|
onclick: move |_| {
|
||||||
|
creating.set(!creating());
|
||||||
|
},
|
||||||
|
i {
|
||||||
|
class: format!("min-w-6 fa-solid {}", if creating() { "fa-xmark" } else { "fa-plus" }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,9 @@
|
|||||||
use crate::components::project_form::ProjectForm;
|
|
||||||
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 crate::components::task_form::TaskForm;
|
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub(crate) fn Home() -> Element {
|
pub(crate) fn Home() -> Element {
|
||||||
rsx! {
|
rsx! {
|
||||||
ProjectForm {}
|
|
||||||
TaskForm {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
30
src/components/layout.rs
Normal file
30
src/components/layout.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
use crate::components::bottom_panel::BottomPanel;
|
||||||
|
use crate::components::navigation::Navigation;
|
||||||
|
use crate::components::task_list::TaskList;
|
||||||
|
use crate::models::category::Category;
|
||||||
|
use crate::route::Route;
|
||||||
|
use chrono::NaiveDate;
|
||||||
|
use dioxus::core_macro::rsx;
|
||||||
|
use dioxus::dioxus_core::Element;
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use crate::components::create_task_button::CreateTaskButton;
|
||||||
|
use crate::components::sticky_bottom::StickyBottom;
|
||||||
|
use crate::components::task_form::TaskForm;
|
||||||
|
use crate::server::tasks::get_tasks_in_category;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub(crate) fn Layout() -> Element {
|
||||||
|
let creating_task = use_signal(|| false);
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
Outlet::<Route> {}
|
||||||
|
StickyBottom {
|
||||||
|
CreateTaskButton {
|
||||||
|
creating: creating_task,
|
||||||
|
}
|
||||||
|
BottomPanel {
|
||||||
|
creating_task: creating_task(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,3 +2,13 @@ pub(crate) mod app;
|
|||||||
pub(crate) mod home;
|
pub(crate) mod home;
|
||||||
pub(crate) mod project_form;
|
pub(crate) mod project_form;
|
||||||
pub(crate) mod task_form;
|
pub(crate) mod task_form;
|
||||||
|
pub(crate) mod task_list;
|
||||||
|
pub(crate) mod pages;
|
||||||
|
pub(crate) mod navigation;
|
||||||
|
pub(crate) mod create_task_button;
|
||||||
|
pub(crate) mod bottom_panel;
|
||||||
|
pub(crate) mod sticky_bottom;
|
||||||
|
pub(crate) mod category_input;
|
||||||
|
pub(crate) mod reoccurrence_input;
|
||||||
|
pub(crate) mod layout;
|
||||||
|
mod navigation_item;
|
||||||
|
83
src/components/navigation.rs
Normal file
83
src/components/navigation.rs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
use crate::components::navigation_item::NavigationItem;
|
||||||
|
use crate::components::task_list::TaskList;
|
||||||
|
use crate::models::category::Category;
|
||||||
|
use crate::route::Route;
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub(crate) fn Navigation(expanded: Signal<bool>) -> Element {
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
class: "grid grid-cols-5 justify-stretch",
|
||||||
|
button {
|
||||||
|
class: format!(
|
||||||
|
"py-4 text-center text-2xl {}",
|
||||||
|
if expanded() { "text-zinc-200" }
|
||||||
|
else { "text-zinc-500" }
|
||||||
|
),
|
||||||
|
onclick: move |_| expanded.set(!expanded()),
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-bars"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
NavigationItem {
|
||||||
|
route: Route::CategoryNextStepsPage,
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-forward"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
NavigationItem {
|
||||||
|
route: Route::CategoryCalendarPage,
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-calendar-days"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
NavigationItem {
|
||||||
|
route: Route::CategoryTodayPage,
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-calendar-day"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
NavigationItem {
|
||||||
|
route: Route::CategoryInboxPage,
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-inbox"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{if expanded() {
|
||||||
|
rsx! {
|
||||||
|
NavigationItem {
|
||||||
|
route: Route::ProjectsPage,
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-list"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
NavigationItem {
|
||||||
|
route: Route::CategoryTrashPage,
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-trash-can"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
NavigationItem {
|
||||||
|
route: Route::CategoryDonePage,
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-check"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
NavigationItem {
|
||||||
|
route: Route::CategoryLongTermPage,
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-water"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
NavigationItem {
|
||||||
|
route: Route::CategoryWaitingForPage,
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-hourglass-half"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { None }}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
src/components/navigation_item.rs
Normal file
21
src/components/navigation_item.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
use dioxus::prelude::*;
|
||||||
|
use crate::components::task_list::TaskList;
|
||||||
|
use crate::models::category::Category;
|
||||||
|
use crate::route::Route;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub(crate) fn NavigationItem(route: Route, children: Element) -> Element {
|
||||||
|
let current_route = use_route::<Route>();
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
Link {
|
||||||
|
to: route.clone(),
|
||||||
|
class: format!(
|
||||||
|
"py-4 text-center text-2xl {}",
|
||||||
|
if current_route == route { "text-zinc-200" }
|
||||||
|
else { "text-zinc-500" }
|
||||||
|
),
|
||||||
|
children
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
src/components/pages/category_calendar_page.rs
Normal file
31
src/components/pages/category_calendar_page.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
use crate::components::bottom_panel::BottomPanel;
|
||||||
|
use crate::components::navigation::Navigation;
|
||||||
|
use crate::components::task_list::TaskList;
|
||||||
|
use crate::models::category::Category;
|
||||||
|
use crate::route::Route;
|
||||||
|
use chrono::NaiveDate;
|
||||||
|
use dioxus::core_macro::rsx;
|
||||||
|
use dioxus::dioxus_core::Element;
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use crate::components::create_task_button::CreateTaskButton;
|
||||||
|
use crate::components::sticky_bottom::StickyBottom;
|
||||||
|
use crate::components::task_form::TaskForm;
|
||||||
|
use crate::server::tasks::get_tasks_in_category;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub(crate) fn CategoryCalendarPage() -> Element {
|
||||||
|
let tasks = use_server_future(
|
||||||
|
move || get_tasks_in_category(Category::Calendar {
|
||||||
|
date: NaiveDate::default(),
|
||||||
|
reoccurrence: None,
|
||||||
|
time: None,
|
||||||
|
})
|
||||||
|
)?.unwrap().unwrap();
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
TaskList {
|
||||||
|
tasks: tasks,
|
||||||
|
class: "pb-36"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
src/components/pages/category_done_page.rs
Normal file
27
src/components/pages/category_done_page.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
use crate::components::bottom_panel::BottomPanel;
|
||||||
|
use crate::components::navigation::Navigation;
|
||||||
|
use crate::components::task_list::TaskList;
|
||||||
|
use crate::models::category::Category;
|
||||||
|
use crate::route::Route;
|
||||||
|
use chrono::NaiveDate;
|
||||||
|
use dioxus::core_macro::rsx;
|
||||||
|
use dioxus::dioxus_core::Element;
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use crate::components::create_task_button::CreateTaskButton;
|
||||||
|
use crate::components::sticky_bottom::StickyBottom;
|
||||||
|
use crate::components::task_form::TaskForm;
|
||||||
|
use crate::server::tasks::get_tasks_in_category;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub(crate) fn CategoryDonePage() -> Element {
|
||||||
|
let tasks = use_server_future(
|
||||||
|
move || get_tasks_in_category(Category::Done)
|
||||||
|
)?.unwrap().unwrap();
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
TaskList {
|
||||||
|
tasks: tasks,
|
||||||
|
class: "pb-36"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
src/components/pages/category_inbox_page.rs
Normal file
27
src/components/pages/category_inbox_page.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
use crate::components::bottom_panel::BottomPanel;
|
||||||
|
use crate::components::navigation::Navigation;
|
||||||
|
use crate::components::task_list::TaskList;
|
||||||
|
use crate::models::category::Category;
|
||||||
|
use crate::route::Route;
|
||||||
|
use chrono::NaiveDate;
|
||||||
|
use dioxus::core_macro::rsx;
|
||||||
|
use dioxus::dioxus_core::Element;
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use crate::components::create_task_button::CreateTaskButton;
|
||||||
|
use crate::components::sticky_bottom::StickyBottom;
|
||||||
|
use crate::components::task_form::TaskForm;
|
||||||
|
use crate::server::tasks::get_tasks_in_category;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub(crate) fn CategoryInboxPage() -> Element {
|
||||||
|
let tasks = use_server_future(
|
||||||
|
move || get_tasks_in_category(Category::Inbox)
|
||||||
|
)?.unwrap().unwrap();
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
TaskList {
|
||||||
|
tasks: tasks,
|
||||||
|
class: "pb-36"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
src/components/pages/category_long_term_page.rs
Normal file
27
src/components/pages/category_long_term_page.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
use crate::components::bottom_panel::BottomPanel;
|
||||||
|
use crate::components::navigation::Navigation;
|
||||||
|
use crate::components::task_list::TaskList;
|
||||||
|
use crate::models::category::Category;
|
||||||
|
use crate::route::Route;
|
||||||
|
use chrono::NaiveDate;
|
||||||
|
use dioxus::core_macro::rsx;
|
||||||
|
use dioxus::dioxus_core::Element;
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use crate::components::create_task_button::CreateTaskButton;
|
||||||
|
use crate::components::sticky_bottom::StickyBottom;
|
||||||
|
use crate::components::task_form::TaskForm;
|
||||||
|
use crate::server::tasks::get_tasks_in_category;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub(crate) fn CategoryLongTermPage() -> Element {
|
||||||
|
let tasks = use_server_future(
|
||||||
|
move || get_tasks_in_category(Category::LongTerm)
|
||||||
|
)?.unwrap().unwrap();
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
TaskList {
|
||||||
|
tasks: tasks,
|
||||||
|
class: "pb-36"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
src/components/pages/category_next_steps_page.rs
Normal file
27
src/components/pages/category_next_steps_page.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
use crate::components::bottom_panel::BottomPanel;
|
||||||
|
use crate::components::navigation::Navigation;
|
||||||
|
use crate::components::task_list::TaskList;
|
||||||
|
use crate::models::category::Category;
|
||||||
|
use crate::route::Route;
|
||||||
|
use chrono::NaiveDate;
|
||||||
|
use dioxus::core_macro::rsx;
|
||||||
|
use dioxus::dioxus_core::Element;
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use crate::components::create_task_button::CreateTaskButton;
|
||||||
|
use crate::components::sticky_bottom::StickyBottom;
|
||||||
|
use crate::components::task_form::TaskForm;
|
||||||
|
use crate::server::tasks::get_tasks_in_category;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub(crate) fn CategoryNextStepsPage() -> Element {
|
||||||
|
let tasks = use_server_future(
|
||||||
|
move || get_tasks_in_category(Category::NextSteps)
|
||||||
|
)?.unwrap().unwrap();
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
TaskList {
|
||||||
|
tasks: tasks,
|
||||||
|
class: "pb-36"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
src/components/pages/category_someday_maybe_page.rs
Normal file
27
src/components/pages/category_someday_maybe_page.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
use crate::components::bottom_panel::BottomPanel;
|
||||||
|
use crate::components::navigation::Navigation;
|
||||||
|
use crate::components::task_list::TaskList;
|
||||||
|
use crate::models::category::Category;
|
||||||
|
use crate::route::Route;
|
||||||
|
use chrono::NaiveDate;
|
||||||
|
use dioxus::core_macro::rsx;
|
||||||
|
use dioxus::dioxus_core::Element;
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use crate::components::create_task_button::CreateTaskButton;
|
||||||
|
use crate::components::sticky_bottom::StickyBottom;
|
||||||
|
use crate::components::task_form::TaskForm;
|
||||||
|
use crate::server::tasks::get_tasks_in_category;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub(crate) fn CategorySomedayMaybePage() -> Element {
|
||||||
|
let tasks = use_server_future(
|
||||||
|
move || get_tasks_in_category(Category::SomedayMaybe)
|
||||||
|
)?.unwrap().unwrap();
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
TaskList {
|
||||||
|
tasks: tasks,
|
||||||
|
class: "pb-36"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
39
src/components/pages/category_today_page.rs
Normal file
39
src/components/pages/category_today_page.rs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
use crate::components::bottom_panel::BottomPanel;
|
||||||
|
use crate::components::create_task_button::CreateTaskButton;
|
||||||
|
use crate::components::navigation::Navigation;
|
||||||
|
use crate::components::sticky_bottom::StickyBottom;
|
||||||
|
use crate::components::task_form::TaskForm;
|
||||||
|
use crate::components::task_list::TaskList;
|
||||||
|
use crate::models::category::Category;
|
||||||
|
use crate::models::task::Task;
|
||||||
|
use crate::route::Route;
|
||||||
|
use crate::schema::tasks::category;
|
||||||
|
use crate::server::tasks::get_tasks_in_category;
|
||||||
|
use chrono::{Local, NaiveDate};
|
||||||
|
use dioxus::core_macro::rsx;
|
||||||
|
use dioxus::dioxus_core::Element;
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub(crate) fn CategoryTodayPage() -> Element {
|
||||||
|
let tasks = use_server_future(
|
||||||
|
move || get_tasks_in_category(Category::Calendar {
|
||||||
|
date: NaiveDate::default(),
|
||||||
|
reoccurrence: None,
|
||||||
|
time: None,
|
||||||
|
})
|
||||||
|
)?.unwrap().unwrap().iter().filter(|task| {
|
||||||
|
if let Category::Calendar { date, .. } = task.category() {
|
||||||
|
*date == Local::now().date_naive()
|
||||||
|
} else {
|
||||||
|
panic!("Unexpected category.");
|
||||||
|
}
|
||||||
|
}).cloned().collect::<Vec<Task>>();
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
TaskList {
|
||||||
|
tasks: tasks,
|
||||||
|
class: "pb-36"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
src/components/pages/category_trash_page.rs
Normal file
27
src/components/pages/category_trash_page.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
use crate::components::bottom_panel::BottomPanel;
|
||||||
|
use crate::components::navigation::Navigation;
|
||||||
|
use crate::components::task_list::TaskList;
|
||||||
|
use crate::models::category::Category;
|
||||||
|
use crate::route::Route;
|
||||||
|
use chrono::NaiveDate;
|
||||||
|
use dioxus::core_macro::rsx;
|
||||||
|
use dioxus::dioxus_core::Element;
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use crate::components::create_task_button::CreateTaskButton;
|
||||||
|
use crate::components::sticky_bottom::StickyBottom;
|
||||||
|
use crate::components::task_form::TaskForm;
|
||||||
|
use crate::server::tasks::get_tasks_in_category;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub(crate) fn CategoryTrashPage() -> Element {
|
||||||
|
let tasks = use_server_future(
|
||||||
|
move || get_tasks_in_category(Category::Trash)
|
||||||
|
)?.unwrap().unwrap();
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
TaskList {
|
||||||
|
tasks: tasks,
|
||||||
|
class: "pb-36"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
src/components/pages/category_waiting_for_page.rs
Normal file
27
src/components/pages/category_waiting_for_page.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
use crate::components::bottom_panel::BottomPanel;
|
||||||
|
use crate::components::navigation::Navigation;
|
||||||
|
use crate::components::task_list::TaskList;
|
||||||
|
use crate::models::category::Category;
|
||||||
|
use crate::route::Route;
|
||||||
|
use chrono::NaiveDate;
|
||||||
|
use dioxus::core_macro::rsx;
|
||||||
|
use dioxus::dioxus_core::Element;
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use crate::components::create_task_button::CreateTaskButton;
|
||||||
|
use crate::components::sticky_bottom::StickyBottom;
|
||||||
|
use crate::components::task_form::TaskForm;
|
||||||
|
use crate::server::tasks::get_tasks_in_category;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub(crate) fn CategoryWaitingForPage() -> Element {
|
||||||
|
let tasks = use_server_future(
|
||||||
|
move || get_tasks_in_category(Category::WaitingFor(String::new()))
|
||||||
|
)?.unwrap().unwrap();
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
TaskList {
|
||||||
|
tasks: tasks,
|
||||||
|
class: "pb-36"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
src/components/pages/mod.rs
Normal file
11
src/components/pages/mod.rs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
pub(crate) mod category_inbox_page;
|
||||||
|
pub(crate) mod category_calendar_page;
|
||||||
|
pub(crate) mod category_today_page;
|
||||||
|
pub(crate) mod category_waiting_for_page;
|
||||||
|
pub(crate) mod category_long_term_page;
|
||||||
|
pub(crate) mod category_next_steps_page;
|
||||||
|
pub(crate) mod category_someday_maybe_page;
|
||||||
|
pub(crate) mod category_done_page;
|
||||||
|
pub(crate) mod category_trash_page;
|
||||||
|
pub(crate) mod not_found_page;
|
||||||
|
pub(crate) mod projects_page;
|
11
src/components/pages/not_found_page.rs
Normal file
11
src/components/pages/not_found_page.rs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
use dioxus::prelude::*;
|
||||||
|
use crate::components::task_list::TaskList;
|
||||||
|
use crate::models::category::Category;
|
||||||
|
use crate::route::Route;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub(crate) fn NotFoundPage(route: Vec<String>) -> Element {
|
||||||
|
rsx! {
|
||||||
|
{"404"}
|
||||||
|
}
|
||||||
|
}
|
7
src/components/pages/projects_page.rs
Normal file
7
src/components/pages/projects_page.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub(crate) fn ProjectsPage() -> Element {
|
||||||
|
rsx! {
|
||||||
|
}
|
||||||
|
}
|
79
src/components/reoccurrence_input.rs
Normal file
79
src/components/reoccurrence_input.rs
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
use crate::models::category::{Category, Reoccurrence, ReoccurrenceInterval};
|
||||||
|
use crate::server::tasks::get_tasks_in_category;
|
||||||
|
use chrono::NaiveDate;
|
||||||
|
use dioxus::core_macro::rsx;
|
||||||
|
use dioxus::dioxus_core::Element;
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use std::fmt::format;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub(crate) fn ReoccurrenceIntervalInput(
|
||||||
|
reoccurrence_interval: Signal<Option<ReoccurrenceInterval>>,
|
||||||
|
class_buttons: Option<&'static str>
|
||||||
|
) -> Element {
|
||||||
|
rsx! {
|
||||||
|
button {
|
||||||
|
r#type: "button",
|
||||||
|
class: format!(
|
||||||
|
"py-2 rounded-lg {} {}",
|
||||||
|
class_buttons.unwrap_or(""),
|
||||||
|
if reoccurrence_interval().is_none() { "bg-zinc-500/50" }
|
||||||
|
else { "bg-zinc-800/50" }
|
||||||
|
),
|
||||||
|
onclick: move |_| {
|
||||||
|
reoccurrence_interval.set(None);
|
||||||
|
},
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-ban"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
button {
|
||||||
|
r#type: "button",
|
||||||
|
class: format!(
|
||||||
|
"py-2 rounded-lg {} {}",
|
||||||
|
class_buttons.unwrap_or(""),
|
||||||
|
if let Some(ReoccurrenceInterval::Day) = reoccurrence_interval()
|
||||||
|
{ "bg-zinc-500/50" }
|
||||||
|
else { "bg-zinc-800/50" }
|
||||||
|
),
|
||||||
|
onclick: move |_| {
|
||||||
|
reoccurrence_interval.set(Some(ReoccurrenceInterval::Day))
|
||||||
|
},
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-sun"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
button {
|
||||||
|
r#type: "button",
|
||||||
|
class: format!(
|
||||||
|
"py-2 rounded-lg {} {}",
|
||||||
|
class_buttons.unwrap_or(""),
|
||||||
|
if let Some(ReoccurrenceInterval::Month) = reoccurrence_interval()
|
||||||
|
{ "bg-zinc-500/50" }
|
||||||
|
else { "bg-zinc-800/50" }
|
||||||
|
),
|
||||||
|
onclick: move |_| {
|
||||||
|
reoccurrence_interval.set(Some(ReoccurrenceInterval::Month))
|
||||||
|
},
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-moon"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
button {
|
||||||
|
r#type: "button",
|
||||||
|
class: format!(
|
||||||
|
"py-2 rounded-lg {} {}",
|
||||||
|
class_buttons.unwrap_or(""),
|
||||||
|
if let Some(ReoccurrenceInterval::Year) = reoccurrence_interval()
|
||||||
|
{ "bg-zinc-500/50" }
|
||||||
|
else { "bg-zinc-800/50" }
|
||||||
|
),
|
||||||
|
onclick: move |_| {
|
||||||
|
reoccurrence_interval.set(Some(ReoccurrenceInterval::Year))
|
||||||
|
},
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-earth-europe"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
src/components/sticky_bottom.rs
Normal file
14
src/components/sticky_bottom.rs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
use dioxus::prelude::*;
|
||||||
|
use crate::components::task_list::TaskList;
|
||||||
|
use crate::models::category::Category;
|
||||||
|
use crate::route::Route;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub(crate) fn StickyBottom(children: Element) -> Element {
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
class: "fixed bottom-0 left-0 right-0 flex flex-col",
|
||||||
|
{children}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,45 +1,65 @@
|
|||||||
use chrono::Duration;
|
use std::fmt::Display;
|
||||||
use crate::models::category::{CalendarTime, Category};
|
use crate::components::category_input::CategoryInput;
|
||||||
|
use crate::components::reoccurrence_input::ReoccurrenceIntervalInput;
|
||||||
|
use crate::models::category::{CalendarTime, Category, Reoccurrence, ReoccurrenceInterval};
|
||||||
use crate::models::task::NewTask;
|
use crate::models::task::NewTask;
|
||||||
use crate::server::projects::get_projects;
|
use crate::server::projects::get_projects;
|
||||||
use crate::server::tasks::create_task;
|
use crate::server::tasks::create_task;
|
||||||
|
use chrono::{Duration, NaiveDate};
|
||||||
use dioxus::core_macro::{component, rsx};
|
use dioxus::core_macro::{component, rsx};
|
||||||
use dioxus::dioxus_core::Element;
|
use dioxus::dioxus_core::Element;
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
use crate::route::Route;
|
||||||
|
|
||||||
|
const REMINDER_OFFSETS: [Option<Duration>; 17] = [
|
||||||
|
None,
|
||||||
|
Some(Duration::days(1)),
|
||||||
|
Some(Duration::hours(12)),
|
||||||
|
Some(Duration::hours(11)),
|
||||||
|
Some(Duration::hours(10)),
|
||||||
|
Some(Duration::hours(9)),
|
||||||
|
Some(Duration::hours(8)),
|
||||||
|
Some(Duration::hours(7)),
|
||||||
|
Some(Duration::hours(6)),
|
||||||
|
Some(Duration::hours(5)),
|
||||||
|
Some(Duration::hours(4)),
|
||||||
|
Some(Duration::hours(3)),
|
||||||
|
Some(Duration::hours(2)),
|
||||||
|
Some(Duration::hours(1)),
|
||||||
|
Some(Duration::minutes(30)),
|
||||||
|
Some(Duration::minutes(10)),
|
||||||
|
Some(Duration::zero()),
|
||||||
|
];
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub(crate) fn TaskForm() -> Element {
|
pub(crate) fn TaskForm() -> Element {
|
||||||
let categories = vec![
|
|
||||||
Category::Inbox,
|
|
||||||
Category::SomedayMaybe,
|
|
||||||
Category::WaitingFor(String::new()),
|
|
||||||
Category::NextSteps,
|
|
||||||
Category::Calendar {
|
|
||||||
date: chrono::Local::now().date_naive(),
|
|
||||||
reoccurance_interval: None,
|
|
||||||
time: None,
|
|
||||||
},
|
|
||||||
Category::LongTerm,
|
|
||||||
];
|
|
||||||
let projects = use_server_future(get_projects)?.unwrap().unwrap();
|
let projects = use_server_future(get_projects)?.unwrap().unwrap();
|
||||||
|
|
||||||
let mut selected_category_index = use_signal::<usize>(|| 0);
|
let route = use_route::<Route>();
|
||||||
let mut category_calendar_is_reoccurring = use_signal::<bool>(|| false);
|
let mut selected_category = use_signal(|| match route {
|
||||||
let mut category_calendar_has_time = use_signal::<bool>(|| false);
|
Route::CategorySomedayMaybePage => Category::SomedayMaybe,
|
||||||
let mut category_calendar_has_reminder = use_signal::<bool>(|| false);
|
Route::CategoryWaitingForPage => Category::WaitingFor(String::new()),
|
||||||
|
Route::CategoryNextStepsPage => Category::NextSteps,
|
||||||
|
Route::CategoryCalendarPage | Route::CategoryTodayPage => Category::Calendar {
|
||||||
|
date: NaiveDate::default(),
|
||||||
|
reoccurrence: None,
|
||||||
|
time: None,
|
||||||
|
},
|
||||||
|
Route::CategoryLongTermPage => Category::LongTerm,
|
||||||
|
_ => Category::Inbox,
|
||||||
|
});
|
||||||
|
let mut category_calendar_reoccurrence_interval = use_signal(|| None);
|
||||||
|
let mut category_calendar_has_time = use_signal(|| false);
|
||||||
|
let mut category_calendar_reminder_offset_index = use_signal(|| REMINDER_OFFSETS.len() - 1);
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
form {
|
form {
|
||||||
onsubmit: move |event| {
|
onsubmit: move |event| {
|
||||||
let categories = categories.clone();
|
|
||||||
async move {
|
async move {
|
||||||
let new_task = NewTask::new(
|
let new_task = NewTask::new(
|
||||||
event.values().get("title").unwrap().as_value(),
|
event.values().get("title").unwrap().as_value(),
|
||||||
event.values().get("deadline").unwrap().as_value().parse().ok(),
|
event.values().get("deadline").unwrap().as_value().parse().ok(),
|
||||||
match &categories[
|
match &selected_category() {
|
||||||
event.values().get("category_index").unwrap()
|
|
||||||
.as_value().parse::<usize>().unwrap()
|
|
||||||
] {
|
|
||||||
Category::WaitingFor(_) => Category::WaitingFor(
|
Category::WaitingFor(_) => Category::WaitingFor(
|
||||||
event.values().get("category_waiting_for").unwrap()
|
event.values().get("category_waiting_for").unwrap()
|
||||||
.as_value()
|
.as_value()
|
||||||
@ -47,10 +67,12 @@ pub(crate) fn TaskForm() -> Element {
|
|||||||
Category::Calendar { .. } => Category::Calendar {
|
Category::Calendar { .. } => Category::Calendar {
|
||||||
date: event.values().get("category_calendar_date").unwrap()
|
date: event.values().get("category_calendar_date").unwrap()
|
||||||
.as_value().parse().unwrap(),
|
.as_value().parse().unwrap(),
|
||||||
reoccurance_interval:
|
reoccurrence: category_calendar_reoccurrence_interval().map(
|
||||||
event.values().get("category_calendar_is_reoccurring").map(
|
|reoccurrence_interval| Reoccurrence::new(
|
||||||
|_| Duration::days(
|
event.values().get("category_calendar_date").unwrap()
|
||||||
event.values().get("category_calendar_reoccurance_interval")
|
.as_value().parse().unwrap(),
|
||||||
|
reoccurrence_interval,
|
||||||
|
event.values().get("category_calendar_reoccurrence_length")
|
||||||
.unwrap().as_value().parse().unwrap()
|
.unwrap().as_value().parse().unwrap()
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@ -58,13 +80,11 @@ pub(crate) fn TaskForm() -> Element {
|
|||||||
.as_value().parse().ok().map(|time|
|
.as_value().parse().ok().map(|time|
|
||||||
CalendarTime::new(
|
CalendarTime::new(
|
||||||
time,
|
time,
|
||||||
event.values().get("category_calendar_has_reminder").map(
|
REMINDER_OFFSETS[
|
||||||
|_| Duration::minutes(
|
|
||||||
event.values()
|
event.values()
|
||||||
.get("category_calendar_reminder_offset").unwrap()
|
.get("category_calendar_reminder_offset_index").unwrap()
|
||||||
.as_value().parse().unwrap()
|
.as_value().parse::<usize>().unwrap()
|
||||||
)
|
]
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -77,133 +97,39 @@ pub(crate) fn TaskForm() -> Element {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
class: "p-4 flex flex-col gap-4",
|
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 {
|
input {
|
||||||
r#type: "text",
|
r#type: "text",
|
||||||
name: "title",
|
name: "title",
|
||||||
required: true,
|
required: true,
|
||||||
placeholder: "title",
|
class: "py-2 px-3 grow bg-zinc-800/50 rounded-lg",
|
||||||
class: "p-2 bg-neutral-700 rounded",
|
id: "input_title"
|
||||||
},
|
},
|
||||||
select {
|
|
||||||
name: "category_index",
|
|
||||||
oninput: move |event| {
|
|
||||||
selected_category_index.set(event.value().parse().unwrap());
|
|
||||||
},
|
|
||||||
class: "p-2 bg-neutral-700 rounded",
|
|
||||||
option {
|
|
||||||
value: 0,
|
|
||||||
"inbox"
|
|
||||||
},
|
|
||||||
option {
|
|
||||||
value: 1,
|
|
||||||
"someday maybe"
|
|
||||||
},
|
|
||||||
option {
|
|
||||||
value: 2,
|
|
||||||
"waiting for"
|
|
||||||
},
|
|
||||||
option {
|
|
||||||
value: 3,
|
|
||||||
"next steps"
|
|
||||||
},
|
|
||||||
option {
|
|
||||||
value: 4,
|
|
||||||
"calendar"
|
|
||||||
},
|
|
||||||
option {
|
|
||||||
value: 5,
|
|
||||||
"long term"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
match categories[selected_category_index()] {
|
|
||||||
Category::WaitingFor(_) => rsx !{
|
|
||||||
input {
|
|
||||||
r#type: "text",
|
|
||||||
name: "category_waiting_for",
|
|
||||||
required: true,
|
|
||||||
class: "p-2 bg-neutral-700 rounded",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Category::Calendar { .. } => rsx !{
|
|
||||||
input {
|
|
||||||
r#type: "date",
|
|
||||||
name: "category_calendar_date",
|
|
||||||
required: true,
|
|
||||||
class: "p-2 bg-neutral-700 rounded",
|
|
||||||
},
|
},
|
||||||
div {
|
div {
|
||||||
input {
|
class: "flex flex-row items-center gap-3",
|
||||||
r#type: "checkbox",
|
|
||||||
name: "category_calendar_is_reoccurring",
|
|
||||||
id: "category_calendar_is_reoccurring",
|
|
||||||
onchange: move |event| {
|
|
||||||
category_calendar_is_reoccurring.set(event.checked());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
label {
|
label {
|
||||||
r#for: "category_calendar_is_reoccurring",
|
r#for: "input_project",
|
||||||
" is reoccurring"
|
class: "min-w-6 text-center",
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-list text-zinc-400/50"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
if category_calendar_is_reoccurring() {
|
|
||||||
input {
|
|
||||||
r#type: "number",
|
|
||||||
name: "category_calendar_reoccurance_interval",
|
|
||||||
required: true,
|
|
||||||
min: 1,
|
|
||||||
placeholder: "reoccurance interval (days)",
|
|
||||||
class: "p-2 bg-neutral-700 rounded",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
input {
|
|
||||||
r#type: "time",
|
|
||||||
name: "category_calendar_time",
|
|
||||||
class: "p-2 bg-neutral-700 rounded",
|
|
||||||
oninput: move |event| {
|
|
||||||
category_calendar_has_time.set(!event.value().is_empty());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
if category_calendar_has_time() {
|
|
||||||
div {
|
|
||||||
input {
|
|
||||||
r#type: "checkbox",
|
|
||||||
name: "category_calendar_has_reminder",
|
|
||||||
value: 0,
|
|
||||||
id: "category_calendar_has_reminder",
|
|
||||||
onchange: move |event| {
|
|
||||||
category_calendar_has_reminder.set(event.checked());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
label {
|
|
||||||
r#for: "category_calendar_has_reminder",
|
|
||||||
" set a reminder"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if category_calendar_has_reminder() {
|
|
||||||
input {
|
|
||||||
r#type: "number",
|
|
||||||
name: "category_calendar_reminder_offset",
|
|
||||||
required: true,
|
|
||||||
min: 0,
|
|
||||||
placeholder: "reminder offset (minutes)",
|
|
||||||
class: "p-2 bg-neutral-700 rounded",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => None
|
|
||||||
},
|
|
||||||
input {
|
|
||||||
r#type: "date",
|
|
||||||
name: "deadline",
|
|
||||||
class: "p-2 bg-neutral-700 rounded",
|
|
||||||
},
|
|
||||||
select {
|
select {
|
||||||
name: "project_id",
|
name: "project_id",
|
||||||
class: "p-2 bg-neutral-700 rounded",
|
class: "px-3.5 py-2.5 bg-zinc-800/50 rounded-lg grow",
|
||||||
|
id: "input_project",
|
||||||
option {
|
option {
|
||||||
value: 0,
|
value: 0,
|
||||||
"none"
|
"None"
|
||||||
},
|
},
|
||||||
for project in projects {
|
for project in projects {
|
||||||
option {
|
option {
|
||||||
@ -212,10 +138,166 @@ pub(crate) fn TaskForm() -> Element {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
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 {
|
||||||
|
r#type: "date",
|
||||||
|
name: "deadline",
|
||||||
|
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.clone(),
|
||||||
|
class: "grow"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match selected_category() {
|
||||||
|
Category::WaitingFor(_) => 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 {
|
||||||
|
r#type: "text",
|
||||||
|
name: "category_waiting_for",
|
||||||
|
required: true,
|
||||||
|
class: "py-2 px-3 bg-zinc-800/50 rounded-lg grow",
|
||||||
|
id: "input_category_waiting_for"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Category::Calendar { .. } => 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: chrono::Local::now().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",
|
||||||
|
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
|
||||||
|
},
|
||||||
|
input {
|
||||||
|
r#type: "number",
|
||||||
|
inputmode: "numeric",
|
||||||
|
name: "category_calendar_reoccurrence_length",
|
||||||
|
disabled: category_calendar_reoccurrence_interval().is_none(),
|
||||||
|
required: true,
|
||||||
|
min: 1,
|
||||||
|
initial_value:
|
||||||
|
if category_calendar_reoccurrence_interval().is_none() { "" }
|
||||||
|
else { "1" },
|
||||||
|
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: REMINDER_OFFSETS.len() as i64 - 1,
|
||||||
|
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
|
||||||
|
},
|
||||||
|
div {
|
||||||
|
class: "flex flex-row justify-end mt-auto",
|
||||||
button {
|
button {
|
||||||
r#type: "submit",
|
r#type: "submit",
|
||||||
"create"
|
class: "py-2 px-4 bg-zinc-300/50 rounded-lg",
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-floppy-disk"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
66
src/components/task_list.rs
Normal file
66
src/components/task_list.rs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
use crate::models::category::Category;
|
||||||
|
use crate::models::task::Task;
|
||||||
|
use crate::server::tasks::get_tasks_in_category;
|
||||||
|
use dioxus::core_macro::rsx;
|
||||||
|
use dioxus::dioxus_core::Element;
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub(crate) fn TaskList(tasks: Vec<Task>, class: Option<&'static str>) -> Element {
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
class: format!("pt-3 px-8 flex flex-col {}", class.unwrap_or("")),
|
||||||
|
for task in tasks {
|
||||||
|
div {
|
||||||
|
class: format!(
|
||||||
|
"pt-5 {} flex flex-row gap-4",
|
||||||
|
if task.deadline().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"
|
||||||
|
}
|
||||||
|
),
|
||||||
|
i {
|
||||||
|
class: "fa-regular fa-square text-3xl text-zinc-600",
|
||||||
|
},
|
||||||
|
div {
|
||||||
|
class: "flex flex-col",
|
||||||
|
div {
|
||||||
|
class: "mt-1 grow",
|
||||||
|
{task.title()}
|
||||||
|
},
|
||||||
|
div {
|
||||||
|
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-clock"
|
||||||
|
},
|
||||||
|
{calendar_time.time().format(" %k:%M").to_string()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,12 @@
|
|||||||
|
use crate::schema::tasks;
|
||||||
use chrono::{Duration, NaiveDate, NaiveTime};
|
use chrono::{Duration, NaiveDate, NaiveTime};
|
||||||
use diesel::deserialize::FromSql;
|
use diesel::deserialize::FromSql;
|
||||||
use diesel::pg::{Pg, PgValue};
|
use diesel::pg::{Pg, PgValue};
|
||||||
use diesel::serialize::{Output, ToSql};
|
use diesel::serialize::{Output, ToSql};
|
||||||
use diesel::sql_types::Jsonb;
|
use diesel::sql_types::{Bool, Jsonb};
|
||||||
use diesel::{AsExpression, FromSqlRow};
|
use diesel::{AsExpression, BoxableExpression, FromSqlRow, PgJsonbExpressionMethods};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::json;
|
||||||
use serde_with::DurationSeconds;
|
use serde_with::DurationSeconds;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
@ -18,8 +20,7 @@ pub enum Category {
|
|||||||
NextSteps,
|
NextSteps,
|
||||||
Calendar {
|
Calendar {
|
||||||
date: NaiveDate,
|
date: NaiveDate,
|
||||||
#[serde_as(as = "Option<DurationSeconds<i64>>")]
|
reoccurrence: Option<Reoccurrence>,
|
||||||
reoccurance_interval: Option<Duration>,
|
|
||||||
time: Option<CalendarTime>,
|
time: Option<CalendarTime>,
|
||||||
},
|
},
|
||||||
LongTerm,
|
LongTerm,
|
||||||
@ -27,17 +28,26 @@ pub enum Category {
|
|||||||
Trash,
|
Trash,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_with::serde_as]
|
impl Category {
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
pub fn eq_sql_predicate(&self) -> Box<dyn BoxableExpression<tasks::table, Pg, SqlType=Bool>> {
|
||||||
pub struct CalendarTime {
|
use crate::schema::tasks::dsl::*;
|
||||||
time: NaiveTime,
|
|
||||||
#[serde_as(as = "Option<DurationSeconds<i64>>")]
|
match self {
|
||||||
reminder_offset: Option<Duration>,
|
Category::Inbox => Box::new(category.contains(json!("Inbox"))),
|
||||||
|
Category::SomedayMaybe => Box::new(category.contains(json!("SomedayMaybe"))),
|
||||||
|
Category::WaitingFor(_) => Box::new(category.has_key("WaitingFor")),
|
||||||
|
Category::NextSteps => Box::new(category.contains(json!("NextSteps"))),
|
||||||
|
Category::Calendar { .. } => Box::new(category.has_key("Calendar")),
|
||||||
|
Category::LongTerm => Box::new(category.contains(json!("LongTerm"))),
|
||||||
|
Category::Done => Box::new(category.contains(json!("Done"))),
|
||||||
|
Category::Trash => Box::new(category.contains(json!("Trash"))),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CalendarTime {
|
impl PartialEq for Category {
|
||||||
pub fn new(time: NaiveTime, reminder_offset: Option<Duration>) -> Self {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
Self { time, reminder_offset }
|
std::mem::discriminant(self) == std::mem::discriminant(other)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,3 +73,53 @@ impl FromSql<Jsonb, Pg> for Category {
|
|||||||
serde_json::from_str(str).map_err(Into::into)
|
serde_json::from_str(str).map_err(Into::into)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub enum ReoccurrenceInterval {
|
||||||
|
Day,
|
||||||
|
Month,
|
||||||
|
Year,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct Reoccurrence {
|
||||||
|
start_date: NaiveDate,
|
||||||
|
interval: ReoccurrenceInterval,
|
||||||
|
length: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Reoccurrence {
|
||||||
|
pub fn new(start_date: NaiveDate, interval: ReoccurrenceInterval, length: u32) -> Self {
|
||||||
|
Self { start_date, interval, length }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interval(&self) -> &ReoccurrenceInterval {
|
||||||
|
&self.interval
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn length(&self) -> u32 {
|
||||||
|
self.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[serde_with::serde_as]
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct CalendarTime {
|
||||||
|
time: NaiveTime,
|
||||||
|
#[serde_as(as = "Option<DurationSeconds<i64>>")]
|
||||||
|
reminder_offset: Option<Duration>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CalendarTime {
|
||||||
|
pub fn new(time: NaiveTime, reminder_offset: Option<Duration>) -> Self {
|
||||||
|
Self { time, reminder_offset }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn time(&self) -> NaiveTime {
|
||||||
|
self.time
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reminder_offset(&self) -> Option<Duration> {
|
||||||
|
self.reminder_offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -7,7 +7,7 @@ use crate::schema::tasks;
|
|||||||
const TITLE_LENGTH_MIN: u64 = 1;
|
const TITLE_LENGTH_MIN: u64 = 1;
|
||||||
const TITLE_LENGTH_MAX: u64 = 255;
|
const TITLE_LENGTH_MAX: u64 = 255;
|
||||||
|
|
||||||
#[derive(Queryable, Selectable, Serialize, Deserialize, Clone, Debug)]
|
#[derive(Queryable, Selectable, Serialize, Deserialize, PartialEq, Clone, Debug)]
|
||||||
#[diesel(table_name = crate::schema::tasks)]
|
#[diesel(table_name = crate::schema::tasks)]
|
||||||
#[diesel(check_for_backend(diesel::pg::Pg))]
|
#[diesel(check_for_backend(diesel::pg::Pg))]
|
||||||
pub struct Task {
|
pub struct Task {
|
||||||
|
@ -1,8 +1,48 @@
|
|||||||
use crate::components::home::Home;
|
use crate::components::home::Home;
|
||||||
|
use crate::components::pages::category_inbox_page::CategoryInboxPage;
|
||||||
|
use crate::components::pages::category_next_steps_page::CategoryNextStepsPage;
|
||||||
|
use crate::components::pages::category_today_page::CategoryTodayPage;
|
||||||
|
use crate::components::pages::category_trash_page::CategoryTrashPage;
|
||||||
|
use crate::components::pages::category_waiting_for_page::CategoryWaitingForPage;
|
||||||
|
use crate::components::pages::category_someday_maybe_page::CategorySomedayMaybePage;
|
||||||
|
use crate::components::pages::category_done_page::CategoryDonePage;
|
||||||
|
use crate::components::pages::category_calendar_page::CategoryCalendarPage;
|
||||||
|
use crate::components::pages::category_long_term_page::CategoryLongTermPage;
|
||||||
|
use crate::components::pages::projects_page::ProjectsPage;
|
||||||
|
use crate::components::pages::not_found_page::NotFoundPage;
|
||||||
|
use crate::components::layout::Layout;
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
use crate::models::category::Category;
|
||||||
|
|
||||||
#[derive(Clone, Routable, Debug, PartialEq)]
|
#[derive(Clone, Routable, Debug, PartialEq)]
|
||||||
|
#[rustfmt::skip]
|
||||||
pub(crate) enum Route {
|
pub(crate) enum Route {
|
||||||
#[route("/")]
|
#[layout(Layout)]
|
||||||
Home {},
|
#[redirect("/", || Route::CategoryTodayPage {})]
|
||||||
|
#[route("/today")]
|
||||||
|
CategoryTodayPage,
|
||||||
|
#[route("/inbox")]
|
||||||
|
CategoryInboxPage,
|
||||||
|
#[route("/someday-maybe")]
|
||||||
|
CategorySomedayMaybePage,
|
||||||
|
#[route("/waiting-for")]
|
||||||
|
CategoryWaitingForPage,
|
||||||
|
#[route("/next-steps")]
|
||||||
|
CategoryNextStepsPage,
|
||||||
|
#[route("/calendar")]
|
||||||
|
CategoryCalendarPage,
|
||||||
|
#[route("/long-term")]
|
||||||
|
CategoryLongTermPage,
|
||||||
|
#[route("/done")]
|
||||||
|
CategoryDonePage,
|
||||||
|
#[route("/trash")]
|
||||||
|
CategoryTrashPage,
|
||||||
|
#[route("/projects")]
|
||||||
|
ProjectsPage,
|
||||||
|
#[end_layout]
|
||||||
|
#[redirect("/", || Route::CategoryTodayPage)]
|
||||||
|
#[route("/:..route")]
|
||||||
|
NotFoundPage {
|
||||||
|
route: Vec<String>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,11 @@ use crate::errors::error::Error;
|
|||||||
use crate::errors::error_vec::ErrorVec;
|
use crate::errors::error_vec::ErrorVec;
|
||||||
use crate::models::task::{NewTask, Task};
|
use crate::models::task::{NewTask, Task};
|
||||||
use crate::server::database_connection::establish_database_connection;
|
use crate::server::database_connection::establish_database_connection;
|
||||||
use diesel::{RunQueryDsl, SelectableHelper};
|
use diesel::{QueryDsl, RunQueryDsl, SelectableHelper};
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
use crate::errors::task_create_error::TaskCreateError;
|
use crate::errors::task_create_error::TaskCreateError;
|
||||||
|
use crate::models::category::Category;
|
||||||
|
|
||||||
#[server]
|
#[server]
|
||||||
pub(crate) async fn create_task(new_task: NewTask)
|
pub(crate) async fn create_task(new_task: NewTask)
|
||||||
@ -43,3 +44,24 @@ pub(crate) async fn create_task(new_task: NewTask)
|
|||||||
|
|
||||||
Ok(new_task)
|
Ok(new_task)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[server]
|
||||||
|
pub(crate) async fn get_tasks_in_category(filtered_category: Category)
|
||||||
|
-> Result<Vec<Task>, ServerFnError<ErrorVec<Error>>> {
|
||||||
|
use crate::schema::tasks::dsl::*;
|
||||||
|
|
||||||
|
let mut connection = establish_database_connection()
|
||||||
|
.map_err::<ErrorVec<Error>, _>(
|
||||||
|
|_| vec![Error::ServerInternal].into()
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let results = tasks
|
||||||
|
.select(Task::as_select())
|
||||||
|
.filter(filtered_category.eq_sql_predicate())
|
||||||
|
.load::<Task>(&mut connection)
|
||||||
|
.map_err::<ErrorVec<Error>, _>(
|
||||||
|
|_| vec![Error::ServerInternal].into()
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user