feat: ability to view tasks in a category (#19)
This commit is contained in:
		
							
								
								
									
										2
									
								
								.idea/dataSources.local.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								.idea/dataSources.local.xml
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="dataSourceStorageLocal" created-in="RR-242.20224.309"> | ||||
|   <component name="dataSourceStorageLocal" created-in="RR-242.21829.114"> | ||||
|     <data-source name="todo_baggins@localhost" uuid="1658668c-c2b8-426d-a22f-16fbad9eff0b"> | ||||
|       <database-info product="PostgreSQL" version="16.4 (Debian 16.4-1.pgdg120+1)" jdbc-version="4.2" driver-name="PostgreSQL JDBC Driver" driver-version="42.6.0" dbms="POSTGRES" exact-version="16.4" exact-driver-version="42.6"> | ||||
|         <identifier-quote-string>"</identifier-quote-string> | ||||
|   | ||||
							
								
								
									
										14
									
								
								.idea/webResources.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								.idea/webResources.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="WebResourcesPaths"> | ||||
|     <contentEntries> | ||||
|       <entry url="file://$PROJECT_DIR$"> | ||||
|         <entryData> | ||||
|           <resourceRoots> | ||||
|             <path value="file://$PROJECT_DIR$/assets" /> | ||||
|           </resourceRoots> | ||||
|         </entryData> | ||||
|       </entry> | ||||
|     </contentEntries> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										451
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										451
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -77,6 +77,17 @@ version = "0.10.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" | ||||
|  | ||||
| [[package]] | ||||
| name = "async-channel" | ||||
| version = "1.9.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" | ||||
| dependencies = [ | ||||
|  "concurrent-queue", | ||||
|  "event-listener 2.5.3", | ||||
|  "futures-core", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "async-channel" | ||||
| version = "2.3.1" | ||||
| @@ -89,6 +100,119 @@ dependencies = [ | ||||
|  "pin-project-lite", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "async-executor" | ||||
| version = "1.13.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d7ebdfa2ebdab6b1760375fa7d6f382b9f486eac35fc994625a00e89280bdbb7" | ||||
| dependencies = [ | ||||
|  "async-task", | ||||
|  "concurrent-queue", | ||||
|  "fastrand 2.1.0", | ||||
|  "futures-lite 2.3.0", | ||||
|  "slab", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "async-global-executor" | ||||
| version = "2.4.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" | ||||
| dependencies = [ | ||||
|  "async-channel 2.3.1", | ||||
|  "async-executor", | ||||
|  "async-io 2.3.4", | ||||
|  "async-lock 3.4.0", | ||||
|  "blocking", | ||||
|  "futures-lite 2.3.0", | ||||
|  "once_cell", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "async-io" | ||||
| version = "1.13.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" | ||||
| dependencies = [ | ||||
|  "async-lock 2.8.0", | ||||
|  "autocfg", | ||||
|  "cfg-if", | ||||
|  "concurrent-queue", | ||||
|  "futures-lite 1.13.0", | ||||
|  "log", | ||||
|  "parking", | ||||
|  "polling 2.8.0", | ||||
|  "rustix 0.37.27", | ||||
|  "slab", | ||||
|  "socket2 0.4.10", | ||||
|  "waker-fn", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "async-io" | ||||
| version = "2.3.4" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" | ||||
| dependencies = [ | ||||
|  "async-lock 3.4.0", | ||||
|  "cfg-if", | ||||
|  "concurrent-queue", | ||||
|  "futures-io", | ||||
|  "futures-lite 2.3.0", | ||||
|  "parking", | ||||
|  "polling 3.7.3", | ||||
|  "rustix 0.38.34", | ||||
|  "slab", | ||||
|  "tracing", | ||||
|  "windows-sys 0.59.0", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "async-lock" | ||||
| version = "2.8.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" | ||||
| dependencies = [ | ||||
|  "event-listener 2.5.3", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "async-lock" | ||||
| version = "3.4.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" | ||||
| dependencies = [ | ||||
|  "event-listener 5.3.1", | ||||
|  "event-listener-strategy", | ||||
|  "pin-project-lite", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "async-std" | ||||
| version = "1.12.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" | ||||
| dependencies = [ | ||||
|  "async-channel 1.9.0", | ||||
|  "async-global-executor", | ||||
|  "async-io 1.13.0", | ||||
|  "async-lock 2.8.0", | ||||
|  "crossbeam-utils", | ||||
|  "futures-channel", | ||||
|  "futures-core", | ||||
|  "futures-io", | ||||
|  "futures-lite 1.13.0", | ||||
|  "gloo-timers", | ||||
|  "kv-log-macro", | ||||
|  "log", | ||||
|  "memchr", | ||||
|  "once_cell", | ||||
|  "pin-project-lite", | ||||
|  "pin-utils", | ||||
|  "slab", | ||||
|  "wasm-bindgen-futures", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "async-task" | ||||
| version = "4.7.1" | ||||
| @@ -226,6 +350,12 @@ dependencies = [ | ||||
|  "serde", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "bitflags" | ||||
| version = "1.3.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" | ||||
|  | ||||
| [[package]] | ||||
| name = "bitflags" | ||||
| version = "2.6.0" | ||||
| @@ -250,10 +380,10 @@ version = "1.6.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" | ||||
| dependencies = [ | ||||
|  "async-channel", | ||||
|  "async-channel 2.3.1", | ||||
|  "async-task", | ||||
|  "futures-io", | ||||
|  "futures-lite", | ||||
|  "futures-lite 2.3.0", | ||||
|  "piper", | ||||
| ] | ||||
|  | ||||
| @@ -341,9 +471,10 @@ dependencies = [ | ||||
|  "iana-time-zone", | ||||
|  "js-sys", | ||||
|  "num-traits", | ||||
|  "pure-rust-locales", | ||||
|  "serde", | ||||
|  "wasm-bindgen", | ||||
|  "windows-targets", | ||||
|  "windows-targets 0.52.6", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @@ -534,7 +665,7 @@ version = "2.2.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "bf97ee7261bb708fa3402fa9c17a54b70e90e3cb98afb3dc8999d5512cb03f94" | ||||
| dependencies = [ | ||||
|  "bitflags", | ||||
|  "bitflags 2.6.0", | ||||
|  "byteorder", | ||||
|  "chrono", | ||||
|  "diesel_derives", | ||||
| @@ -830,6 +961,17 @@ dependencies = [ | ||||
|  "tracing-wasm", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "dioxus-query" | ||||
| version = "0.5.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "2c84184c06ee2823957aa3ef939da6a04f0ab934f007745eb65dadb105bde241" | ||||
| dependencies = [ | ||||
|  "dioxus-lib", | ||||
|  "futures-util", | ||||
|  "instant", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "dioxus-router" | ||||
| version = "0.5.6" | ||||
| @@ -1016,6 +1158,16 @@ version = "1.0.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" | ||||
|  | ||||
| [[package]] | ||||
| name = "errno" | ||||
| version = "0.3.9" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" | ||||
| dependencies = [ | ||||
|  "libc", | ||||
|  "windows-sys 0.52.0", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "euclid" | ||||
| version = "0.22.10" | ||||
| @@ -1026,6 +1178,12 @@ dependencies = [ | ||||
|  "serde", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "event-listener" | ||||
| version = "2.5.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" | ||||
|  | ||||
| [[package]] | ||||
| name = "event-listener" | ||||
| version = "5.3.1" | ||||
| @@ -1043,10 +1201,19 @@ version = "0.5.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" | ||||
| dependencies = [ | ||||
|  "event-listener", | ||||
|  "event-listener 5.3.1", | ||||
|  "pin-project-lite", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "fastrand" | ||||
| version = "1.9.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" | ||||
| dependencies = [ | ||||
|  "instant", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "fastrand" | ||||
| version = "2.1.0" | ||||
| @@ -1122,13 +1289,31 @@ version = "0.3.30" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" | ||||
|  | ||||
| [[package]] | ||||
| name = "futures-lite" | ||||
| version = "1.13.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" | ||||
| dependencies = [ | ||||
|  "fastrand 1.9.0", | ||||
|  "futures-core", | ||||
|  "futures-io", | ||||
|  "memchr", | ||||
|  "parking", | ||||
|  "pin-project-lite", | ||||
|  "waker-fn", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "futures-lite" | ||||
| version = "2.3.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" | ||||
| dependencies = [ | ||||
|  "fastrand 2.1.0", | ||||
|  "futures-core", | ||||
|  "futures-io", | ||||
|  "parking", | ||||
|  "pin-project-lite", | ||||
| ] | ||||
|  | ||||
| @@ -1362,6 +1547,8 @@ version = "0.2.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" | ||||
| dependencies = [ | ||||
|  "futures-channel", | ||||
|  "futures-core", | ||||
|  "js-sys", | ||||
|  "wasm-bindgen", | ||||
| ] | ||||
| @@ -1453,6 +1640,12 @@ version = "0.3.9" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" | ||||
|  | ||||
| [[package]] | ||||
| name = "hermit-abi" | ||||
| version = "0.4.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" | ||||
|  | ||||
| [[package]] | ||||
| name = "hex" | ||||
| version = "0.4.3" | ||||
| @@ -1617,6 +1810,18 @@ dependencies = [ | ||||
|  "serde", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "instant" | ||||
| version = "0.1.13" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" | ||||
| dependencies = [ | ||||
|  "cfg-if", | ||||
|  "js-sys", | ||||
|  "wasm-bindgen", | ||||
|  "web-sys", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "internment" | ||||
| version = "0.7.5" | ||||
| @@ -1659,6 +1864,17 @@ version = "0.3.15" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" | ||||
|  | ||||
| [[package]] | ||||
| name = "io-lifetimes" | ||||
| version = "1.0.11" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" | ||||
| dependencies = [ | ||||
|  "hermit-abi 0.3.9", | ||||
|  "libc", | ||||
|  "windows-sys 0.48.0", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "itoa" | ||||
| version = "1.0.11" | ||||
| @@ -1680,7 +1896,7 @@ version = "0.7.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" | ||||
| dependencies = [ | ||||
|  "bitflags", | ||||
|  "bitflags 2.6.0", | ||||
|  "serde", | ||||
|  "unicode-segmentation", | ||||
| ] | ||||
| @@ -1698,6 +1914,15 @@ dependencies = [ | ||||
|  "semver", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "kv-log-macro" | ||||
| version = "1.0.7" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" | ||||
| dependencies = [ | ||||
|  "log", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "lazy_static" | ||||
| version = "1.5.0" | ||||
| @@ -1710,6 +1935,18 @@ version = "0.2.155" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" | ||||
|  | ||||
| [[package]] | ||||
| name = "linux-raw-sys" | ||||
| version = "0.3.8" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" | ||||
|  | ||||
| [[package]] | ||||
| name = "linux-raw-sys" | ||||
| version = "0.4.14" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" | ||||
|  | ||||
| [[package]] | ||||
| name = "lock_api" | ||||
| version = "0.4.12" | ||||
| @@ -1725,6 +1962,9 @@ name = "log" | ||||
| version = "0.4.22" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" | ||||
| dependencies = [ | ||||
|  "value-bag", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "longest-increasing-subsequence" | ||||
| @@ -1790,10 +2030,10 @@ version = "1.0.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" | ||||
| dependencies = [ | ||||
|  "hermit-abi", | ||||
|  "hermit-abi 0.3.9", | ||||
|  "libc", | ||||
|  "wasi", | ||||
|  "windows-sys", | ||||
|  "windows-sys 0.52.0", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @@ -1894,7 +2134,7 @@ dependencies = [ | ||||
|  "libc", | ||||
|  "redox_syscall", | ||||
|  "smallvec", | ||||
|  "windows-targets", | ||||
|  "windows-targets 0.52.6", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @@ -1952,10 +2192,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" | ||||
| dependencies = [ | ||||
|  "atomic-waker", | ||||
|  "fastrand", | ||||
|  "fastrand 2.1.0", | ||||
|  "futures-io", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "polling" | ||||
| version = "2.8.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" | ||||
| dependencies = [ | ||||
|  "autocfg", | ||||
|  "bitflags 1.3.2", | ||||
|  "cfg-if", | ||||
|  "concurrent-queue", | ||||
|  "libc", | ||||
|  "log", | ||||
|  "pin-project-lite", | ||||
|  "windows-sys 0.48.0", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "polling" | ||||
| version = "3.7.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" | ||||
| dependencies = [ | ||||
|  "cfg-if", | ||||
|  "concurrent-queue", | ||||
|  "hermit-abi 0.4.0", | ||||
|  "pin-project-lite", | ||||
|  "rustix 0.38.34", | ||||
|  "tracing", | ||||
|  "windows-sys 0.59.0", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "powerfmt" | ||||
| version = "0.2.0" | ||||
| @@ -2023,6 +2294,12 @@ dependencies = [ | ||||
|  "unicode-ident", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "pure-rust-locales" | ||||
| version = "0.8.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "1190fd18ae6ce9e137184f207593877e70f39b015040156b1e05081cdfe3733a" | ||||
|  | ||||
| [[package]] | ||||
| name = "quote" | ||||
| version = "1.0.36" | ||||
| @@ -2068,7 +2345,7 @@ version = "0.5.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" | ||||
| dependencies = [ | ||||
|  "bitflags", | ||||
|  "bitflags 2.6.0", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @@ -2121,6 +2398,33 @@ dependencies = [ | ||||
|  "semver", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "rustix" | ||||
| version = "0.37.27" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" | ||||
| dependencies = [ | ||||
|  "bitflags 1.3.2", | ||||
|  "errno", | ||||
|  "io-lifetimes", | ||||
|  "libc", | ||||
|  "linux-raw-sys 0.3.8", | ||||
|  "windows-sys 0.48.0", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "rustix" | ||||
| version = "0.38.34" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" | ||||
| dependencies = [ | ||||
|  "bitflags 2.6.0", | ||||
|  "errno", | ||||
|  "libc", | ||||
|  "linux-raw-sys 0.4.14", | ||||
|  "windows-sys 0.52.0", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "rustversion" | ||||
| version = "1.0.17" | ||||
| @@ -2433,6 +2737,16 @@ version = "1.13.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" | ||||
|  | ||||
| [[package]] | ||||
| name = "socket2" | ||||
| version = "0.4.10" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" | ||||
| dependencies = [ | ||||
|  "libc", | ||||
|  "winapi", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "socket2" | ||||
| version = "0.5.7" | ||||
| @@ -2440,7 +2754,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" | ||||
| dependencies = [ | ||||
|  "libc", | ||||
|  "windows-sys", | ||||
|  "windows-sys 0.52.0", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @@ -2583,10 +2897,12 @@ checksum = "c7c4ceeeca15c8384bbc3e011dbd8fccb7f068a440b752b7d9b32ceb0ca0e2e8" | ||||
| name = "todo-baggins" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "async-std", | ||||
|  "chrono", | ||||
|  "diesel", | ||||
|  "dioxus", | ||||
|  "dioxus-logger", | ||||
|  "dioxus-query", | ||||
|  "dotenvy", | ||||
|  "serde", | ||||
|  "serde_json", | ||||
| @@ -2609,9 +2925,9 @@ dependencies = [ | ||||
|  "parking_lot", | ||||
|  "pin-project-lite", | ||||
|  "signal-hook-registry", | ||||
|  "socket2", | ||||
|  "socket2 0.5.7", | ||||
|  "tokio-macros", | ||||
|  "windows-sys", | ||||
|  "windows-sys 0.52.0", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @@ -2686,7 +3002,7 @@ version = "0.5.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" | ||||
| dependencies = [ | ||||
|  "bitflags", | ||||
|  "bitflags 2.6.0", | ||||
|  "bytes", | ||||
|  "futures-util", | ||||
|  "http 1.1.0", | ||||
| @@ -2922,6 +3238,12 @@ version = "0.1.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" | ||||
|  | ||||
| [[package]] | ||||
| name = "value-bag" | ||||
| version = "1.9.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" | ||||
|  | ||||
| [[package]] | ||||
| name = "vcpkg" | ||||
| version = "0.2.15" | ||||
| @@ -2934,6 +3256,12 @@ version = "0.9.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" | ||||
|  | ||||
| [[package]] | ||||
| name = "waker-fn" | ||||
| version = "1.2.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" | ||||
|  | ||||
| [[package]] | ||||
| name = "wasi" | ||||
| version = "0.11.0+wasi-snapshot-preview1" | ||||
| @@ -3058,7 +3386,16 @@ version = "0.52.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" | ||||
| dependencies = [ | ||||
|  "windows-targets", | ||||
|  "windows-targets 0.52.6", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "windows-sys" | ||||
| version = "0.48.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" | ||||
| dependencies = [ | ||||
|  "windows-targets 0.48.5", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @@ -3067,7 +3404,31 @@ version = "0.52.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" | ||||
| dependencies = [ | ||||
|  "windows-targets", | ||||
|  "windows-targets 0.52.6", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "windows-sys" | ||||
| version = "0.59.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" | ||||
| dependencies = [ | ||||
|  "windows-targets 0.52.6", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "windows-targets" | ||||
| version = "0.48.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" | ||||
| dependencies = [ | ||||
|  "windows_aarch64_gnullvm 0.48.5", | ||||
|  "windows_aarch64_msvc 0.48.5", | ||||
|  "windows_i686_gnu 0.48.5", | ||||
|  "windows_i686_msvc 0.48.5", | ||||
|  "windows_x86_64_gnu 0.48.5", | ||||
|  "windows_x86_64_gnullvm 0.48.5", | ||||
|  "windows_x86_64_msvc 0.48.5", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @@ -3076,28 +3437,46 @@ version = "0.52.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" | ||||
| dependencies = [ | ||||
|  "windows_aarch64_gnullvm", | ||||
|  "windows_aarch64_msvc", | ||||
|  "windows_i686_gnu", | ||||
|  "windows_aarch64_gnullvm 0.52.6", | ||||
|  "windows_aarch64_msvc 0.52.6", | ||||
|  "windows_i686_gnu 0.52.6", | ||||
|  "windows_i686_gnullvm", | ||||
|  "windows_i686_msvc", | ||||
|  "windows_x86_64_gnu", | ||||
|  "windows_x86_64_gnullvm", | ||||
|  "windows_x86_64_msvc", | ||||
|  "windows_i686_msvc 0.52.6", | ||||
|  "windows_x86_64_gnu 0.52.6", | ||||
|  "windows_x86_64_gnullvm 0.52.6", | ||||
|  "windows_x86_64_msvc 0.52.6", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_aarch64_gnullvm" | ||||
| version = "0.48.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_aarch64_gnullvm" | ||||
| version = "0.52.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_aarch64_msvc" | ||||
| version = "0.48.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_aarch64_msvc" | ||||
| version = "0.52.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_i686_gnu" | ||||
| version = "0.48.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_i686_gnu" | ||||
| version = "0.52.6" | ||||
| @@ -3110,24 +3489,48 @@ version = "0.52.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_i686_msvc" | ||||
| version = "0.48.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_i686_msvc" | ||||
| version = "0.52.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_x86_64_gnu" | ||||
| version = "0.48.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_x86_64_gnu" | ||||
| version = "0.52.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_x86_64_gnullvm" | ||||
| version = "0.48.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_x86_64_gnullvm" | ||||
| version = "0.52.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_x86_64_msvc" | ||||
| version = "0.48.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_x86_64_msvc" | ||||
| version = "0.52.6" | ||||
|   | ||||
| @@ -7,7 +7,7 @@ edition = "2021" | ||||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||
|  | ||||
| [dependencies] | ||||
| chrono = { version = "0.4.38", features = ["serde"] } | ||||
| chrono = { version = "0.4.38", features = ["serde", "unstable-locales"] } | ||||
| diesel = { version = "2.2.2", features = ["chrono", "postgres", "postgres_backend", "serde_json"] } | ||||
|  | ||||
| dioxus = { version = "0.5", features = ["fullstack", "router"] } | ||||
| @@ -21,6 +21,8 @@ serde_json = "1.0.125" | ||||
| tracing = "0.1.40" | ||||
| tracing-wasm = "0.2.1" | ||||
| serde_with = { version = "3.9.0", features = ["chrono_0_4"] } | ||||
| async-std = "1.12.0" | ||||
| dioxus-query = "0.5.1" | ||||
|  | ||||
| [features] | ||||
| default = [] | ||||
|   | ||||
| @@ -31,10 +31,15 @@ watch_path = ["src", "assets"] | ||||
|  | ||||
| # CSS style file | ||||
|  | ||||
| style = ["/styles/tailwind_output.css"] | ||||
| style = [ | ||||
|     "/styles/tailwind_output.css", | ||||
|     "/styles/fonts.css", | ||||
|     "/styles/input_number_arrows.css", | ||||
|     "/styles/input_range.css" | ||||
| ] | ||||
|  | ||||
| # Javascript code file | ||||
| script = [] | ||||
| script = ["https://kit.fontawesome.com/3c1b409f8f.js"] | ||||
|  | ||||
| [web.resource.dev] | ||||
|  | ||||
|   | ||||
							
								
								
									
										17
									
								
								assets/styles/fonts.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								assets/styles/fonts.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| @layer base { | ||||
|     @font-face { | ||||
|         font-family: Inter; | ||||
|         font-style: normal; | ||||
|         font-weight: 100 900; | ||||
|         font-display: swap; | ||||
|         src: url("/fonts/inter_variable.woff2") format("woff2"); | ||||
|     } | ||||
|  | ||||
|     @font-face { | ||||
|         font-family: Inter; | ||||
|         font-style: italic; | ||||
|         font-weight: 100 900; | ||||
|         font-display: swap; | ||||
|         src: url("/fonts/inter_variable_italic.woff2") format("woff2"); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										10
									
								
								assets/styles/input_number_arrows.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								assets/styles/input_number_arrows.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| input::-webkit-outer-spin-button, | ||||
| input::-webkit-inner-spin-button { | ||||
|     display: none; | ||||
|     appearance: none; | ||||
|     margin: 0; | ||||
| } | ||||
|  | ||||
| input[type="number"] { | ||||
|     appearance:textfield; | ||||
| } | ||||
							
								
								
									
										63
									
								
								assets/styles/input_range.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								assets/styles/input_range.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | ||||
| input[type="range"], | ||||
| input[type="range"]::-webkit-slider-runnable-track, | ||||
| input[type="range"]::-webkit-slider-thumb { | ||||
|     appearance: none; | ||||
| } | ||||
|  | ||||
| input[type="range"] { | ||||
|     background: transparent; | ||||
| } | ||||
|  | ||||
| input[type="range"]::-moz-range-thumb { | ||||
|     width: 1.25rem; | ||||
|     height: 1.25rem; | ||||
|     background: rgba(228 228 231); | ||||
|     border: 0; | ||||
|     border-radius: 0.5rem; | ||||
| } | ||||
|  | ||||
| input[type="range"]::-moz-range-progress { | ||||
|     background: #525259; | ||||
|     height: 0.5rem; | ||||
|     border-radius: 0.25rem; | ||||
| } | ||||
|  | ||||
| input[type="range"]::-moz-range-track { | ||||
|     background: rgba(39 39 42 / 50%); | ||||
|     height: 0.5rem; | ||||
|     border-radius: 0.25rem; | ||||
| } | ||||
|  | ||||
| input[type="range"].input-range-reverse::-moz-range-progress { | ||||
|     background: #2d2d31; | ||||
|     height: 0.5rem; | ||||
|     border-radius: 0.25rem; | ||||
| } | ||||
|  | ||||
| input[type="range"].input-range-reverse::-moz-range-track { | ||||
|     background: rgba(113 113 122 / 50%); | ||||
|     height: 0.5rem; | ||||
|     border-radius: 0.25rem; | ||||
| } | ||||
|  | ||||
| input[type="range"]::-webkit-slider-thumb { | ||||
|     width: 1.25rem; | ||||
|     height: 1.25rem; | ||||
|     background: rgba(228 228 231); | ||||
|     border: 0; | ||||
|     border-radius: 0.5rem; | ||||
|     position: relative; | ||||
|     top: -0.4rem; | ||||
| } | ||||
|  | ||||
| input[type="range"]::-webkit-slider-runnable-track { | ||||
|     background: rgba(39 39 42 / 50%); | ||||
|     height: 0.5rem; | ||||
|     border-radius: 0.25rem; | ||||
| } | ||||
|  | ||||
| input[type="range"].input-range-reverse::-webkit-slider-runnable-track { | ||||
|     background: rgba(39 39 42 / 50%); | ||||
|     height: 0.5rem; | ||||
|     border-radius: 0.25rem; | ||||
| } | ||||
| @@ -2,12 +2,16 @@ use crate::route::Route; | ||||
| use dioxus::core_macro::rsx; | ||||
| use dioxus::dioxus_core::Element; | ||||
| use dioxus::prelude::*; | ||||
| use dioxus_query::prelude::{use_init_query_client}; | ||||
| use crate::query::{QueryErrors, QueryKey, QueryValue}; | ||||
|  | ||||
| #[component] | ||||
| pub(crate) fn App() -> Element { | ||||
|     use_init_query_client::<QueryValue, QueryErrors, QueryKey>(); | ||||
|      | ||||
|     rsx! { | ||||
|         div { | ||||
|             class: "min-h-screen text-white bg-neutral-800", | ||||
|             class: "min-h-screen text-zinc-200 bg-zinc-800 pt-4 pb-36", | ||||
|             Router::<Route> {} | ||||
|         } | ||||
|     } | ||||
|   | ||||
							
								
								
									
										62
									
								
								src/components/bottom_panel.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/components/bottom_panel.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | ||||
| use dioxus::prelude::*; | ||||
| use crate::components::navigation::Navigation; | ||||
| use crate::components::project_form::ProjectForm; | ||||
| use crate::components::task_form::TaskForm; | ||||
| use crate::route::Route; | ||||
|  | ||||
| #[component] | ||||
| pub(crate) fn BottomPanel(display_form: Signal<bool>) -> 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 current_route = use_route(); | ||||
|  | ||||
|     use_effect(use_reactive(&display_form, move |creating_task| { | ||||
|         if creating_task() { | ||||
|             expanded.set(true); | ||||
|         } else { | ||||
|             spawn(async move { | ||||
|                 // Necessary for a smooth – not instant – height transition. | ||||
|                 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 (display_form(), current_route, navigation_expanded()) { | ||||
|                     (false, _, false) => "h-[64px]", | ||||
|                     (false, _, true) => "h-[128px]", | ||||
|                     (true, Route::ProjectsPage, _) => "h-[128px]", | ||||
|                     (true, _, _) => "h-[448px]", | ||||
|                 } | ||||
|             ), | ||||
|             if expanded() { | ||||
|                 match current_route { | ||||
|                     Route::ProjectsPage => rsx! { | ||||
|                         ProjectForm { | ||||
|                             on_successful_submit: move |_| { | ||||
|                                 display_form.set(false); | ||||
|                             } | ||||
|                         } | ||||
|                     }, | ||||
|                     _ => rsx! { | ||||
|                         TaskForm { | ||||
|                             on_successful_submit: move |_| { | ||||
|                                 display_form.set(false); | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } else { | ||||
|                 Navigation { | ||||
|                     expanded: navigation_expanded, | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										102
									
								
								src/components/category_input.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								src/components/category_input.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,102 @@ | ||||
| use crate::models::category::Category; | ||||
| use chrono::NaiveDate; | ||||
| use dioxus::core_macro::rsx; | ||||
| use dioxus::dioxus_core::Element; | ||||
| use dioxus::prelude::*; | ||||
|  | ||||
| #[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" | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										16
									
								
								src/components/create_task_button.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/components/create_task_button.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| use dioxus::prelude::*; | ||||
|  | ||||
| #[component] | ||||
| pub(crate) fn CreateButton(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::dioxus_core::Element; | ||||
| use dioxus::prelude::*; | ||||
| use crate::components::task_form::TaskForm; | ||||
|  | ||||
| #[component] | ||||
| pub(crate) fn Home() -> Element { | ||||
|     rsx! { | ||||
|         ProjectForm {} | ||||
|         TaskForm {} | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										24
									
								
								src/components/layout.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/components/layout.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| use crate::components::bottom_panel::BottomPanel; | ||||
| use crate::route::Route; | ||||
| use dioxus::core_macro::rsx; | ||||
| use dioxus::dioxus_core::Element; | ||||
| use dioxus::prelude::*; | ||||
| use crate::components::create_task_button::CreateButton; | ||||
| use crate::components::sticky_bottom::StickyBottom; | ||||
|  | ||||
| #[component] | ||||
| pub(crate) fn Layout() -> Element { | ||||
|     let display_form = use_signal(|| false); | ||||
|      | ||||
|     rsx! { | ||||
|         Outlet::<Route> {} | ||||
|         StickyBottom { | ||||
|             CreateButton { | ||||
|                 creating: display_form, | ||||
|             } | ||||
|             BottomPanel { | ||||
|                 display_form: display_form, | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -2,3 +2,13 @@ pub(crate) mod app; | ||||
| pub(crate) mod home; | ||||
| pub(crate) mod project_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; | ||||
| pub(crate) mod navigation_item; | ||||
|   | ||||
							
								
								
									
										81
									
								
								src/components/navigation.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								src/components/navigation.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | ||||
| use crate::components::navigation_item::NavigationItem; | ||||
| 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 }} | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										19
									
								
								src/components/navigation_item.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/components/navigation_item.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| use dioxus::prelude::*; | ||||
| 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 | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										78
									
								
								src/components/pages/category_calendar_page.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/components/pages/category_calendar_page.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | ||||
| use crate::models::category::Category; | ||||
| use chrono::{Datelike, Local, Locale}; | ||||
| use dioxus::core_macro::rsx; | ||||
| use dioxus::dioxus_core::Element; | ||||
| use dioxus::prelude::*; | ||||
| use dioxus_query::prelude::QueryResult; | ||||
| use crate::components::task_list::TaskList; | ||||
| use crate::query::QueryValue; | ||||
| use crate::query::tasks::use_tasks_in_category_query; | ||||
| use crate::models::task::Task; | ||||
|  | ||||
| const CALENDAR_LENGTH_DAYS: usize = 366 * 3; | ||||
|  | ||||
| #[component] | ||||
| pub(crate) fn CategoryCalendarPage() -> Element { | ||||
|     let tasks = use_tasks_in_category_query(Category::Calendar { | ||||
|         date: Local::now().date_naive(), | ||||
|         reoccurrence: None, | ||||
|         time: None, | ||||
|     }); | ||||
|     let tasks_query_result = tasks.result(); | ||||
|  | ||||
|     rsx! { | ||||
|         match tasks_query_result.value() { | ||||
|             QueryResult::Ok(QueryValue::Tasks(tasks)) | ||||
|             | QueryResult::Loading(Some(QueryValue::Tasks(tasks))) => { | ||||
|                 let today_date = Local::now().date_naive(); | ||||
|                  | ||||
|                 rsx! { | ||||
|                     div { | ||||
|                         class: "pt-4 flex flex-col gap-8", | ||||
|                         for date_current in today_date.iter_days().take(CALENDAR_LENGTH_DAYS) { | ||||
|                             div { | ||||
|                                 class: "flex flex-col gap-4", | ||||
|                                 div { | ||||
|                                     class: "px-8 flex flex-row items-center gap-2 font-bold", | ||||
|                                     div { | ||||
|                                         class: "pt-1", | ||||
|                                         { | ||||
|                                             date_current | ||||
|                                             .format_localized( | ||||
|                                                 format!( | ||||
|                                                     "%A %-d. %B{}",  | ||||
|                                                     if date_current.year() != today_date.year() {" %Y"}  | ||||
|                                                     else {""} | ||||
|                                                 ).as_str(), | ||||
|                                                 Locale::en_US | ||||
|                                             ) | ||||
|                                             .to_string() | ||||
|                                         } | ||||
|                                     } | ||||
|                                 } | ||||
|                                 TaskList { | ||||
|                                     tasks: tasks.iter().filter(|task| { | ||||
|                                         if let Category::Calendar { date, .. } = task.category() { | ||||
|                                             *date == date_current | ||||
|                                         } else { | ||||
|                                             panic!("Unexpected category."); | ||||
|                                         } | ||||
|                                     }).cloned().collect::<Vec<Task>>() | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                     }    | ||||
|                 } | ||||
|             }, | ||||
|             QueryResult::Loading(None) => rsx! { | ||||
|                 // TODO: Add a loading indicator. | ||||
|             }, | ||||
|             QueryResult::Err(errors) => rsx! { | ||||
|                 div { | ||||
|                     "Errors occurred: {errors:?}" | ||||
|                 } | ||||
|             }, | ||||
|             value => panic!("Unexpected query result: {value:?}") | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										14
									
								
								src/components/pages/category_done_page.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/components/pages/category_done_page.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| use crate::models::category::Category; | ||||
| use dioxus::core_macro::rsx; | ||||
| use dioxus::dioxus_core::Element; | ||||
| use dioxus::prelude::*; | ||||
| use crate::components::pages::category_page::CategoryPage; | ||||
|  | ||||
| #[component] | ||||
| pub(crate) fn CategoryDonePage() -> Element { | ||||
|     rsx! { | ||||
|         CategoryPage { | ||||
|             category: Category::Done, | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										14
									
								
								src/components/pages/category_inbox_page.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/components/pages/category_inbox_page.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| use crate::models::category::Category; | ||||
| use dioxus::core_macro::rsx; | ||||
| use dioxus::dioxus_core::Element; | ||||
| use dioxus::prelude::*; | ||||
| use crate::components::pages::category_page::CategoryPage; | ||||
|  | ||||
| #[component] | ||||
| pub(crate) fn CategoryInboxPage() -> Element { | ||||
|     rsx! { | ||||
|         CategoryPage { | ||||
|             category: Category::Inbox, | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										14
									
								
								src/components/pages/category_long_term_page.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/components/pages/category_long_term_page.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| use crate::models::category::Category; | ||||
| use dioxus::core_macro::rsx; | ||||
| use dioxus::dioxus_core::Element; | ||||
| use dioxus::prelude::*; | ||||
| use crate::components::pages::category_page::CategoryPage; | ||||
|  | ||||
| #[component] | ||||
| pub(crate) fn CategoryLongTermPage() -> Element { | ||||
|     rsx! { | ||||
|         CategoryPage { | ||||
|             category: Category::LongTerm, | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										14
									
								
								src/components/pages/category_next_steps_page.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/components/pages/category_next_steps_page.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| use crate::models::category::Category; | ||||
| use dioxus::core_macro::rsx; | ||||
| use dioxus::dioxus_core::Element; | ||||
| use dioxus::prelude::*; | ||||
| use crate::components::pages::category_page::CategoryPage; | ||||
|  | ||||
| #[component] | ||||
| pub(crate) fn CategoryNextStepsPage() -> Element { | ||||
|     rsx! { | ||||
|         CategoryPage { | ||||
|             category: Category::NextSteps, | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										33
									
								
								src/components/pages/category_page.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/components/pages/category_page.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| use crate::components::task_list::TaskList; | ||||
| use crate::models::category::Category; | ||||
| use crate::query::tasks::use_tasks_in_category_query; | ||||
| use crate::query::QueryValue; | ||||
| use dioxus::core_macro::rsx; | ||||
| use dioxus::dioxus_core::Element; | ||||
| use dioxus::prelude::*; | ||||
| use dioxus_query::prelude::QueryResult; | ||||
|  | ||||
| #[component] | ||||
| pub(crate) fn CategoryPage(category: Category) -> Element { | ||||
|     let tasks_query = use_tasks_in_category_query(category); | ||||
|     let tasks_query_result = tasks_query.result(); | ||||
|  | ||||
|     match tasks_query_result.value() { | ||||
|         QueryResult::Ok(QueryValue::Tasks(tasks)) | ||||
|         | QueryResult::Loading(Some(QueryValue::Tasks(tasks))) => rsx! { | ||||
|             TaskList { | ||||
|                 tasks: tasks.clone(), | ||||
|                 class: "pb-36" | ||||
|             } | ||||
|         }, | ||||
|         QueryResult::Loading(None) => rsx! { | ||||
|             // TODO: Add a loading indicator. | ||||
|         }, | ||||
|         QueryResult::Err(errors) => rsx! { | ||||
|             div { | ||||
|                 "Errors occurred: {errors:?}" | ||||
|             } | ||||
|         }, | ||||
|         value => panic!("Unexpected query result: {value:?}") | ||||
|     } | ||||
| } | ||||
							
								
								
									
										14
									
								
								src/components/pages/category_someday_maybe_page.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/components/pages/category_someday_maybe_page.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| use crate::models::category::Category; | ||||
| use dioxus::core_macro::rsx; | ||||
| use dioxus::dioxus_core::Element; | ||||
| use dioxus::prelude::*; | ||||
| use crate::components::pages::category_page::CategoryPage; | ||||
|  | ||||
| #[component] | ||||
| pub(crate) fn CategorySomedayMaybePage() -> Element { | ||||
|     rsx! { | ||||
|         CategoryPage { | ||||
|             category: Category::SomedayMaybe, | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										160
									
								
								src/components/pages/category_today_page.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								src/components/pages/category_today_page.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,160 @@ | ||||
| use crate::components::task_list::TaskList; | ||||
| use crate::models::category::Category; | ||||
| use crate::models::task::Task; | ||||
| use crate::query::tasks::use_tasks_in_category_query; | ||||
| use crate::query::QueryValue; | ||||
| use chrono::{Local, Locale}; | ||||
| use dioxus::prelude::*; | ||||
| use dioxus_query::prelude::QueryResult; | ||||
|  | ||||
| #[component] | ||||
| pub(crate) fn CategoryTodayPage() -> Element { | ||||
|     let today_date = Local::now().date_naive(); | ||||
|      | ||||
|     let calendar_tasks_query = use_tasks_in_category_query(Category::Calendar { | ||||
|         date: today_date, | ||||
|         reoccurrence: None, | ||||
|         time: None, | ||||
|     }); | ||||
|     let calendar_tasks_query_result = calendar_tasks_query.result(); | ||||
|  | ||||
|     let long_term_tasks_query = use_tasks_in_category_query(Category::LongTerm); | ||||
|     let long_term_tasks_query_result = long_term_tasks_query.result(); | ||||
|  | ||||
|     rsx! { | ||||
|         div { | ||||
|             class: "pt-4 flex flex-col gap-8", | ||||
|             match long_term_tasks_query_result.value() { | ||||
|                 QueryResult::Ok(QueryValue::Tasks(tasks)) | ||||
|                 | QueryResult::Loading(Some(QueryValue::Tasks(tasks))) => rsx! { | ||||
|                     div { | ||||
|                         class: "flex flex-col gap-4", | ||||
|                         div { | ||||
|                             class: "px-8 flex flex-row items-center gap-2 font-bold", | ||||
|                             i { | ||||
|                                 class: "fa-solid fa-water text-xl w-6 text-center" | ||||
|                             } | ||||
|                             div { | ||||
|                                 class: "mt-1", | ||||
|                                 "Long-term" | ||||
|                             } | ||||
|                         } | ||||
|                         div { | ||||
|                             for task in tasks { | ||||
|                                 div { | ||||
|                                     key: "{task.id()}", | ||||
|                                     class: format!( | ||||
|                                         "px-8 pt-5 {} flex flex-row gap-4", | ||||
|                                         if task.deadline().is_some() { | ||||
|                                             "pb-0.5" | ||||
|                                         } else { | ||||
|                                             "pb-5" | ||||
|                                         } | ||||
|                                     ), | ||||
|                                     div { | ||||
|                                         class: "flex flex-col", | ||||
|                                         div { | ||||
|                                             class: "mt grow font-medium", | ||||
|                                             {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()} | ||||
|                                                 } | ||||
|                                             } | ||||
|                                         } | ||||
|                                     } | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 }, | ||||
|                 QueryResult::Loading(None) => rsx! { | ||||
|                     // TODO: Add a loading indicator. | ||||
|                 }, | ||||
|                 QueryResult::Err(errors) => rsx! { | ||||
|                     div { | ||||
|                         "Errors occurred: {errors:?}" | ||||
|                     } | ||||
|                 }, | ||||
|                 value => panic!("Unexpected query result: {value:?}") | ||||
|             } | ||||
|             match calendar_tasks_query_result.value() { | ||||
|                 QueryResult::Ok(QueryValue::Tasks(tasks)) | ||||
|                 | QueryResult::Loading(Some(QueryValue::Tasks(tasks))) => { | ||||
|                     let today_tasks = tasks.iter().filter(|task| { | ||||
|                         if let Category::Calendar { date, .. } = task.category() { | ||||
|                             *date == today_date | ||||
|                         } else { | ||||
|                             panic!("Unexpected category."); | ||||
|                         } | ||||
|                     }).cloned().collect::<Vec<Task>>(); | ||||
|                     let overdue_tasks = tasks.iter().filter(|task| { | ||||
|                         if let Category::Calendar { date, .. } = task.category() { | ||||
|                             *date < today_date | ||||
|                         } else { | ||||
|                             panic!("Unexpected category."); | ||||
|                         } | ||||
|                     }).cloned().collect::<Vec<Task>>(); | ||||
|          | ||||
|                     rsx! { | ||||
|                         if !overdue_tasks.is_empty() { | ||||
|                             div { | ||||
|                                 class: "flex flex-col gap-4", | ||||
|                                 div { | ||||
|                                     class: "px-8 flex flex-row items-center gap-2 font-bold", | ||||
|                                     i { | ||||
|                                         class: "fa-solid fa-calendar-xmark text-xl w-6 text-center" | ||||
|                                     } | ||||
|                                     div { | ||||
|                                         class: "mt-1", | ||||
|                                         "Overdue" | ||||
|                                     } | ||||
|                                 } | ||||
|                                 TaskList { | ||||
|                                     tasks: overdue_tasks, | ||||
|                                     class: "pb-3" | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                         div { | ||||
|                             class: "flex flex-col gap-4", | ||||
|                             div { | ||||
|                                 class: "px-8 flex flex-row items-center gap-2 font-bold", | ||||
|                                 i { | ||||
|                                     class: "fa-solid fa-calendar-check text-xl w-6 text-center" | ||||
|                                 } | ||||
|                                 div { | ||||
|                                     class: "mt-1", | ||||
|                                     { | ||||
|                                         today_date | ||||
|                                         .format_localized("Today, %A %-d. %B", Locale::en_US) | ||||
|                                         .to_string() | ||||
|                                     } | ||||
|                                 } | ||||
|                             } | ||||
|                             TaskList { | ||||
|                                 tasks: today_tasks | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 }, | ||||
|                 QueryResult::Loading(None) => rsx! { | ||||
|                     // TODO: Add a loading indicator. | ||||
|                 }, | ||||
|                 QueryResult::Err(errors) => rsx! { | ||||
|                     div { | ||||
|                         "Errors occurred: {errors:?}" | ||||
|                     } | ||||
|                 }, | ||||
|                 value => panic!("Unexpected query result: {value:?}") | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										14
									
								
								src/components/pages/category_trash_page.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/components/pages/category_trash_page.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| use crate::models::category::Category; | ||||
| use dioxus::core_macro::rsx; | ||||
| use dioxus::dioxus_core::Element; | ||||
| use dioxus::prelude::*; | ||||
| use crate::components::pages::category_page::CategoryPage; | ||||
|  | ||||
| #[component] | ||||
| pub(crate) fn CategoryTrashPage() -> Element { | ||||
|     rsx! { | ||||
|         CategoryPage { | ||||
|             category: Category::Trash, | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										14
									
								
								src/components/pages/category_waiting_for_page.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/components/pages/category_waiting_for_page.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| use crate::models::category::Category; | ||||
| use dioxus::core_macro::rsx; | ||||
| use dioxus::dioxus_core::Element; | ||||
| use dioxus::prelude::*; | ||||
| use crate::components::pages::category_page::CategoryPage; | ||||
|  | ||||
| #[component] | ||||
| pub(crate) fn CategoryWaitingForPage() -> Element { | ||||
|     rsx! { | ||||
|         CategoryPage { | ||||
|             category: Category::WaitingFor(String::new()), | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										12
									
								
								src/components/pages/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/components/pages/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| 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; | ||||
| pub(crate) mod category_page; | ||||
							
								
								
									
										8
									
								
								src/components/pages/not_found_page.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/components/pages/not_found_page.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| use dioxus::prelude::*; | ||||
|  | ||||
| #[component] | ||||
| pub(crate) fn NotFoundPage(route: Vec<String>) -> Element { | ||||
|     rsx! { | ||||
|         {"404"} | ||||
|     } | ||||
| } | ||||
							
								
								
									
										36
									
								
								src/components/pages/projects_page.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/components/pages/projects_page.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| use dioxus::prelude::*; | ||||
| use dioxus_query::prelude::QueryResult; | ||||
| use crate::query::projects::use_projects_query; | ||||
| use crate::query::QueryValue; | ||||
|  | ||||
| #[component] | ||||
| pub(crate) fn ProjectsPage() -> Element { | ||||
|     let projects_query = use_projects_query(); | ||||
|      | ||||
|     rsx! { | ||||
|         match projects_query.result().value() { | ||||
|             QueryResult::Ok(QueryValue::Projects(projects)) | ||||
|             | QueryResult::Loading(Some(QueryValue::Projects(projects))) => rsx! { | ||||
|                 div { | ||||
|                     class: "flex flex-col", | ||||
|                     for project in projects { | ||||
|                         div { | ||||
|                             key: "{project.id()}", | ||||
|                             class: "px-8 py-4", | ||||
|                             {project.title()} | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             QueryResult::Loading(None) => rsx! { | ||||
|                 // TODO: Add a loading indicator. | ||||
|             }, | ||||
|             QueryResult::Err(errors) => rsx! { | ||||
|                 div { | ||||
|                     "Errors occurred: {errors:?}" | ||||
|                 } | ||||
|             }, | ||||
|             value => panic!("Unexpected query result: {value:?}") | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -3,9 +3,13 @@ use crate::server::projects::create_project; | ||||
| use dioxus::core_macro::{component, rsx}; | ||||
| use dioxus::dioxus_core::Element; | ||||
| use dioxus::prelude::*; | ||||
| use dioxus_query::prelude::use_query_client; | ||||
| use crate::query::{QueryErrors, QueryKey, QueryValue}; | ||||
|  | ||||
| #[component] | ||||
| pub(crate) fn ProjectForm() -> Element { | ||||
| pub(crate) fn ProjectForm(on_successful_submit: EventHandler<()>) -> Element { | ||||
|     let query_client = use_query_client::<QueryValue, QueryErrors, QueryKey>(); | ||||
|      | ||||
|     rsx! { | ||||
|         form { | ||||
|             onsubmit: move |event| { | ||||
| @@ -14,17 +18,39 @@ pub(crate) fn ProjectForm() -> Element { | ||||
|                         event.values().get("title").unwrap().as_value() | ||||
|                     ); | ||||
|                     let _ = create_project(new_project).await; | ||||
|                     query_client.invalidate_queries(&[ | ||||
|                         QueryKey::Projects | ||||
|                     ]); | ||||
|                     on_successful_submit.call(()); | ||||
|                 } | ||||
|             }, | ||||
|             input { | ||||
|                 r#type: "text", | ||||
|                 name: "title", | ||||
|                 required: true, | ||||
|                 placeholder: "title" | ||||
|             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 { | ||||
|                     r#type: "text", | ||||
|                     name: "title", | ||||
|                     required: true, | ||||
|                     class: "py-2 px-3 grow bg-zinc-800/50 rounded-lg", | ||||
|                     id: "input_title" | ||||
|                 } | ||||
|             } | ||||
|             button { | ||||
|                 r#type: "submit", | ||||
|                 "create" | ||||
|             div { | ||||
|                 class: "flex flex-row justify-end mt-auto", | ||||
|                 button { | ||||
|                     r#type: "submit", | ||||
|                     class: "py-2 px-4 bg-zinc-300/50 rounded-lg", | ||||
|                     i { | ||||
|                         class: "fa-solid fa-floppy-disk" | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
							
								
								
									
										76
									
								
								src/components/reoccurrence_input.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								src/components/reoccurrence_input.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | ||||
| use crate::models::category::ReoccurrenceInterval; | ||||
| use dioxus::core_macro::rsx; | ||||
| use dioxus::dioxus_core::Element; | ||||
| use dioxus::prelude::*; | ||||
|  | ||||
| #[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" | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										11
									
								
								src/components/sticky_bottom.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/components/sticky_bottom.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| use dioxus::prelude::*; | ||||
|  | ||||
| #[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,68 @@ | ||||
| use chrono::Duration; | ||||
| 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}; | ||||
| use crate::models::task::NewTask; | ||||
| use crate::server::projects::get_projects; | ||||
| use crate::server::tasks::create_task; | ||||
| use chrono::{Duration, NaiveDate}; | ||||
| use dioxus::core_macro::{component, rsx}; | ||||
| use dioxus::dioxus_core::Element; | ||||
| use dioxus::prelude::*; | ||||
| use dioxus_query::prelude::use_query_client; | ||||
| use crate::query::{QueryErrors, QueryKey, QueryValue}; | ||||
| 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] | ||||
| 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, | ||||
|     ]; | ||||
| pub(crate) fn TaskForm(on_successful_submit: EventHandler<()>) -> Element { | ||||
|     let projects = use_server_future(get_projects)?.unwrap().unwrap(); | ||||
|  | ||||
|     let mut selected_category_index = use_signal::<usize>(|| 0); | ||||
|     let mut category_calendar_is_reoccurring = use_signal::<bool>(|| false); | ||||
|     let mut category_calendar_has_time = use_signal::<bool>(|| false); | ||||
|     let mut category_calendar_has_reminder = use_signal::<bool>(|| false); | ||||
|     let route = use_route::<Route>(); | ||||
|     let selected_category = use_signal(|| match route {  | ||||
|         Route::CategorySomedayMaybePage => Category::SomedayMaybe, | ||||
|         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 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); | ||||
|  | ||||
|     let query_client = use_query_client::<QueryValue, QueryErrors, QueryKey>(); | ||||
|      | ||||
|     rsx! { | ||||
|         form { | ||||
|             onsubmit: move |event| { | ||||
|                 let categories = categories.clone(); | ||||
|                 async move { | ||||
|                     let new_task = NewTask::new( | ||||
|                         event.values().get("title").unwrap().as_value(), | ||||
|                         event.values().get("deadline").unwrap().as_value().parse().ok(), | ||||
|                         match &categories[ | ||||
|                             event.values().get("category_index").unwrap() | ||||
|                             .as_value().parse::<usize>().unwrap() | ||||
|                         ] { | ||||
|                         match &selected_category() { | ||||
|                             Category::WaitingFor(_) => Category::WaitingFor( | ||||
|                                 event.values().get("category_waiting_for").unwrap() | ||||
|                                 .as_value() | ||||
| @@ -47,24 +70,24 @@ pub(crate) fn TaskForm() -> Element { | ||||
|                             Category::Calendar { .. } => Category::Calendar { | ||||
|                                 date: event.values().get("category_calendar_date").unwrap() | ||||
|                                 .as_value().parse().unwrap(), | ||||
|                                 reoccurance_interval: | ||||
|                                 event.values().get("category_calendar_is_reoccurring").map( | ||||
|                                     |_| Duration::days( | ||||
|                                         event.values().get("category_calendar_reoccurance_interval") | ||||
|                                 reoccurrence: category_calendar_reoccurrence_interval().map( | ||||
|                                     |reoccurrence_interval| Reoccurrence::new( | ||||
|                                         event.values().get("category_calendar_date").unwrap() | ||||
|                                         .as_value().parse().unwrap(), | ||||
|                                         reoccurrence_interval, | ||||
|                                         event.values().get("category_calendar_reoccurrence_length") | ||||
|                                         .unwrap().as_value().parse().unwrap() | ||||
|                                     ) | ||||
|                                 ), | ||||
|                                 time: event.values().get("category_calendar_time").unwrap() | ||||
|                                     .as_value().parse().ok().map(|time| | ||||
|                                 .as_value().parse().ok().map(|time| | ||||
|                                     CalendarTime::new( | ||||
|                                         time, | ||||
|                                         event.values().get("category_calendar_has_reminder").map( | ||||
|                                             |_| Duration::minutes( | ||||
|                                                 event.values() | ||||
|                                                 .get("category_calendar_reminder_offset").unwrap() | ||||
|                                                 .as_value().parse().unwrap() | ||||
|                                             ) | ||||
|                                         ) | ||||
|                                         REMINDER_OFFSETS[ | ||||
|                                             event.values() | ||||
|                                             .get("category_calendar_reminder_offset_index").unwrap() | ||||
|                                             .as_value().parse::<usize>().unwrap() | ||||
|                                         ] | ||||
|                                     ) | ||||
|                                 ) | ||||
|                             }, | ||||
| @@ -74,148 +97,215 @@ pub(crate) fn TaskForm() -> Element { | ||||
|                         .as_value().parse::<i32>().ok().filter(|&id| id > 0), | ||||
|                     ); | ||||
|                     let _ = create_task(new_task).await; | ||||
|                     query_client.invalidate_queries(&[ | ||||
|                         QueryKey::Tasks,  | ||||
|                         QueryKey::TasksInCategory(selected_category()) | ||||
|                     ]); | ||||
|                     on_successful_submit.call(()); | ||||
|                 } | ||||
|             }, | ||||
|             class: "p-4 flex flex-col gap-4", | ||||
|             input { | ||||
|                 r#type: "text", | ||||
|                 name: "title", | ||||
|                 required: true, | ||||
|                 placeholder: "title", | ||||
|                 class: "p-2 bg-neutral-700 rounded", | ||||
|             }, | ||||
|             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", | ||||
|             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" | ||||
|                     }, | ||||
|                 }, | ||||
|                 Category::Calendar { .. } => rsx !{ | ||||
|                     input { | ||||
|                         r#type: "date", | ||||
|                         name: "category_calendar_date", | ||||
|                         required: true, | ||||
|                         class: "p-2 bg-neutral-700 rounded", | ||||
|                 input { | ||||
|                     r#type: "text", | ||||
|                     name: "title", | ||||
|                     required: true, | ||||
|                     class: "py-2 px-3 grow bg-zinc-800/50 rounded-lg", | ||||
|                     id: "input_title" | ||||
|                 }, | ||||
|             }, | ||||
|             div { | ||||
|                 class: "flex flex-row items-center gap-3", | ||||
|                 label { | ||||
|                     r#for: "input_project", | ||||
|                     class: "min-w-6 text-center", | ||||
|                     i { | ||||
|                         class: "fa-solid fa-list text-zinc-400/50" | ||||
|                     } | ||||
|                 }, | ||||
|                 select { | ||||
|                     name: "project_id", | ||||
|                     class: "px-3.5 py-2.5 bg-zinc-800/50 rounded-lg grow", | ||||
|                     id: "input_project", | ||||
|                     option { | ||||
|                         value: 0, | ||||
|                         "None" | ||||
|                     }, | ||||
|                     for project in projects { | ||||
|                         option { | ||||
|                             value: project.id().to_string(), | ||||
|                             {project.title()} | ||||
|                         } | ||||
|                     } | ||||
|                 }, | ||||
|             }, | ||||
|             div { | ||||
|                 class: "flex flex-row items-center gap-3", | ||||
|                 label { | ||||
|                     r#for: "input_deadline", | ||||
|                     class: "min-w-6 text-center", | ||||
|                     i { | ||||
|                         class: "fa-solid fa-bomb text-zinc-400/50" | ||||
|                     } | ||||
|                 }, | ||||
|                 input { | ||||
|                     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, | ||||
|                     class: "grow" | ||||
|                 } | ||||
|             } | ||||
|             match selected_category() { | ||||
|                 Category::WaitingFor(_) => rsx! { | ||||
|                     div { | ||||
|                         input { | ||||
|                             r#type: "checkbox", | ||||
|                             name: "category_calendar_is_reoccurring", | ||||
|                             id: "category_calendar_is_reoccurring", | ||||
|                             onchange: move |event| { | ||||
|                                 category_calendar_is_reoccurring.set(event.checked()); | ||||
|                         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" | ||||
|                             } | ||||
|                         }, | ||||
|                         label { | ||||
|                             r#for: "category_calendar_is_reoccurring", | ||||
|                             " is reoccurring" | ||||
|                         } | ||||
|                     }, | ||||
|                     if category_calendar_is_reoccurring() { | ||||
|                         input { | ||||
|                             r#type: "number", | ||||
|                             name: "category_calendar_reoccurance_interval", | ||||
|                             r#type: "text", | ||||
|                             name: "category_waiting_for", | ||||
|                             required: true, | ||||
|                             min: 1, | ||||
|                             placeholder: "reoccurance interval (days)", | ||||
|                             class: "p-2 bg-neutral-700 rounded", | ||||
|                             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()); | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                     }, | ||||
|                     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()); | ||||
|                     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: "checkbox", | ||||
|                                 name: "category_calendar_has_reminder", | ||||
|                                 value: 0, | ||||
|                                 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", | ||||
|                                 onchange: move |event| { | ||||
|                                     category_calendar_has_reminder.set(event.checked()); | ||||
|                                 oninput: move |event| { | ||||
|                                     category_calendar_reminder_offset_index.set( | ||||
|                                         event.value().parse().unwrap() | ||||
|                                     ); | ||||
|                                 } | ||||
|                             }, | ||||
|                             label { | ||||
|                                 r#for: "category_calendar_has_reminder", | ||||
|                                 " set a reminder" | ||||
|                                 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())} | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                     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 { | ||||
|                 name: "project_id", | ||||
|                 class: "p-2 bg-neutral-700 rounded", | ||||
|                 option { | ||||
|                     value: 0, | ||||
|                     "none" | ||||
|                 }, | ||||
|                 for project in projects { | ||||
|                     option { | ||||
|                         value: project.id().to_string(), | ||||
|                         {project.title()} | ||||
|             div { | ||||
|                 class: "flex flex-row justify-end mt-auto", | ||||
|                 button { | ||||
|                     r#type: "submit", | ||||
|                     class: "py-2 px-4 bg-zinc-300/50 rounded-lg", | ||||
|                     i { | ||||
|                         class: "fa-solid fa-floppy-disk" | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             button { | ||||
|                 r#type: "submit", | ||||
|                 "create" | ||||
|             } | ||||
|         } | ||||
| } | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										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 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!("flex flex-col {}", class.unwrap_or("")), | ||||
|             for task in tasks { | ||||
|                 div { | ||||
|                     key: "{task.id()}", | ||||
|                     class: format!( | ||||
|                         "px-8 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 font-medium", | ||||
|                             {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()} | ||||
|                                     } | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -4,6 +4,7 @@ mod models; | ||||
| mod route; | ||||
| mod schema; | ||||
| mod server; | ||||
| mod query; | ||||
|  | ||||
| use components::app::App; | ||||
| use dioxus::prelude::*; | ||||
|   | ||||
| @@ -1,10 +1,13 @@ | ||||
| use std::hash::Hash; | ||||
| use crate::schema::tasks; | ||||
| use chrono::{Duration, NaiveDate, NaiveTime}; | ||||
| use diesel::deserialize::FromSql; | ||||
| use diesel::pg::{Pg, PgValue}; | ||||
| use diesel::serialize::{Output, ToSql}; | ||||
| use diesel::sql_types::Jsonb; | ||||
| use diesel::{AsExpression, FromSqlRow}; | ||||
| use diesel::sql_types::{Bool, Jsonb}; | ||||
| use diesel::{AsExpression, BoxableExpression, FromSqlRow, PgJsonbExpressionMethods}; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use serde_json::json; | ||||
| use serde_with::DurationSeconds; | ||||
| use std::io::Write; | ||||
|  | ||||
| @@ -18,8 +21,7 @@ pub enum Category { | ||||
|     NextSteps, | ||||
|     Calendar { | ||||
|         date: NaiveDate, | ||||
|         #[serde_as(as = "Option<DurationSeconds<i64>>")] | ||||
|         reoccurance_interval: Option<Duration>, | ||||
|         reoccurrence: Option<Reoccurrence>, | ||||
|         time: Option<CalendarTime>, | ||||
|     }, | ||||
|     LongTerm, | ||||
| @@ -27,20 +29,37 @@ pub enum Category { | ||||
|     Trash, | ||||
| } | ||||
|  | ||||
| #[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 Category { | ||||
|     pub fn eq_sql_predicate(&self) -> Box<dyn BoxableExpression<tasks::table, Pg, SqlType=Bool>> { | ||||
|         use crate::schema::tasks::dsl::*; | ||||
|  | ||||
| impl CalendarTime { | ||||
|     pub fn new(time: NaiveTime, reminder_offset: Option<Duration>) -> Self { | ||||
|         Self { time, reminder_offset } | ||||
|         match self { | ||||
|             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 Hash for Category { | ||||
|     fn hash<H: std::hash::Hasher>(&self, state: &mut H) { | ||||
|         std::mem::discriminant(self).hash(state); | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl PartialEq for Category { | ||||
|     fn eq(&self, other: &Self) -> bool { | ||||
|         std::mem::discriminant(self) == std::mem::discriminant(other) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Eq for Category {} | ||||
|  | ||||
| impl ToSql<Jsonb, Pg> for Category { | ||||
|     fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> diesel::serialize::Result { | ||||
|         let json = serde_json::to_string(self)?; | ||||
| @@ -63,3 +82,53 @@ impl FromSql<Jsonb, Pg> for Category { | ||||
|         serde_json::from_str(str).map_err(Into::into) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Serialize, Deserialize, Hash, Clone, Debug)] | ||||
| pub enum ReoccurrenceInterval { | ||||
|     Day, | ||||
|     Month, | ||||
|     Year, | ||||
| } | ||||
|  | ||||
| #[derive(Serialize, Deserialize, Hash, 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, Hash, 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 | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -6,7 +6,7 @@ use validator::Validate; | ||||
| const TITLE_LENGTH_MIN: u64 = 1; | ||||
| 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::projects)] | ||||
| #[diesel(check_for_backend(diesel::pg::Pg))] | ||||
| pub struct Project { | ||||
|   | ||||
| @@ -1,13 +1,13 @@ | ||||
| use crate::models::category::Category; | ||||
| use crate::schema::tasks; | ||||
| use diesel::prelude::*; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use validator::Validate; | ||||
| use crate::models::category::Category; | ||||
| use crate::schema::tasks; | ||||
|  | ||||
| const TITLE_LENGTH_MIN: u64 = 1; | ||||
| 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(check_for_backend(diesel::pg::Pg))] | ||||
| pub struct Task { | ||||
|   | ||||
							
								
								
									
										26
									
								
								src/query/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/query/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| use crate::errors::error::Error; | ||||
| use crate::errors::error_vec::ErrorVec; | ||||
| use crate::models::category::Category; | ||||
| use crate::models::project::Project; | ||||
| use crate::models::task::Task; | ||||
|  | ||||
| pub(crate) mod tasks; | ||||
| pub(crate) mod projects; | ||||
|  | ||||
| #[derive(PartialEq, Debug)] | ||||
| pub(crate) enum QueryValue { | ||||
|     Tasks(Vec<Task>), | ||||
|     Projects(Vec<Project>), | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub(crate) enum QueryErrors { | ||||
|     Error(ErrorVec<Error>), | ||||
| } | ||||
|  | ||||
| #[derive(PartialEq, Eq, Hash, Clone, Debug)] | ||||
| pub(crate) enum QueryKey { | ||||
|     Tasks, | ||||
|     TasksInCategory(Category), | ||||
|     Projects, | ||||
| } | ||||
							
								
								
									
										20
									
								
								src/query/projects.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/query/projects.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| use crate::query::{QueryErrors, QueryKey, QueryValue}; | ||||
| use crate::server::projects::get_projects; | ||||
| use dioxus::prelude::ServerFnError; | ||||
| use dioxus_query::prelude::{use_get_query, QueryResult, UseQuery}; | ||||
|  | ||||
| pub(crate) fn use_projects_query() -> UseQuery<QueryValue, QueryErrors, QueryKey> { | ||||
|     use_get_query([QueryKey::Projects, QueryKey::Tasks], fetch_projects) | ||||
| } | ||||
|  | ||||
| async fn fetch_projects(keys: Vec<QueryKey>) -> QueryResult<QueryValue, QueryErrors> { | ||||
|     if let Some(QueryKey::Projects) = keys.first() { | ||||
|         match get_projects().await { | ||||
|             Ok(projects) => Ok(QueryValue::Projects(projects)), | ||||
|             Err(ServerFnError::WrappedServerError(errors)) => Err(QueryErrors::Error(errors)), | ||||
|             Err(error) => panic!("Unexpected error: {:?}", error) | ||||
|         }.into() | ||||
|     } else { | ||||
|         panic!("Unexpected query keys: {:?}", keys); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										24
									
								
								src/query/tasks.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/query/tasks.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| use dioxus::prelude::ServerFnError; | ||||
| use dioxus_query::prelude::{use_get_query, QueryResult, UseQuery}; | ||||
| use crate::models::category::Category; | ||||
| use crate::query::{QueryErrors, QueryKey, QueryValue}; | ||||
| use crate::server::tasks::get_tasks_in_category; | ||||
|  | ||||
|  | ||||
|  | ||||
| pub(crate) fn use_tasks_in_category_query(category: Category) | ||||
|                                           -> UseQuery<QueryValue, QueryErrors, QueryKey> { | ||||
|     use_get_query([QueryKey::TasksInCategory(category), QueryKey::Tasks], fetch_tasks_in_category) | ||||
| } | ||||
|  | ||||
| async fn fetch_tasks_in_category(keys: Vec<QueryKey>) -> QueryResult<QueryValue, QueryErrors> { | ||||
|     if let Some(QueryKey::TasksInCategory(category)) = keys.first() { | ||||
|         match get_tasks_in_category(category.clone()).await { | ||||
|             Ok(tasks) => Ok(QueryValue::Tasks(tasks)), | ||||
|             Err(ServerFnError::WrappedServerError(errors)) => Err(QueryErrors::Error(errors)), | ||||
|             Err(error) => panic!("Unexpected error: {:?}", error) | ||||
|         }.into() | ||||
|     } else { | ||||
|         panic!("Unexpected query keys: {:?}", keys); | ||||
|     } | ||||
| } | ||||
| @@ -1,8 +1,48 @@ | ||||
| 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::*; | ||||
|  | ||||
| // All variants have the same postfix because they have to match the component names. | ||||
| #[allow(clippy::enum_variant_names)] | ||||
| #[derive(Clone, Routable, Debug, PartialEq)] | ||||
| #[rustfmt::skip] | ||||
| pub(crate) enum Route { | ||||
|     #[route("/")] | ||||
|     Home {}, | ||||
|     #[layout(Layout)] | ||||
|         #[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::models::task::{NewTask, Task}; | ||||
| use crate::server::database_connection::establish_database_connection; | ||||
| use diesel::{RunQueryDsl, SelectableHelper}; | ||||
| use diesel::{QueryDsl, RunQueryDsl, SelectableHelper}; | ||||
| use dioxus::prelude::*; | ||||
| use validator::Validate; | ||||
| use crate::errors::task_create_error::TaskCreateError; | ||||
| use crate::models::category::Category; | ||||
|  | ||||
| #[server] | ||||
| 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) | ||||
| } | ||||
|  | ||||
| #[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) | ||||
| } | ||||
|   | ||||
| @@ -13,21 +13,3 @@ html, body, #main { | ||||
| } | ||||
|  | ||||
| /* stylelint-enable */ | ||||
|  | ||||
| @layer base { | ||||
|     @font-face { | ||||
|         font-family: Inter; | ||||
|         font-style: normal; | ||||
|         font-weight: 100 900; | ||||
|         font-display: swap; | ||||
|         src: url("/fonts/inter_variable.woff2") format("woff2"); | ||||
|     } | ||||
|  | ||||
|     @font-face { | ||||
|         font-family: Inter; | ||||
|         font-style: italic; | ||||
|         font-weight: 100 900; | ||||
|         font-display: swap; | ||||
|         src: url("/fonts/inter_variable_italic.woff2") format("woff2"); | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user