diff --git a/Cargo.lock b/Cargo.lock index ed472ee..d1044cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5479,9 +5479,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", "once_cell", @@ -5490,9 +5490,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", @@ -5517,9 +5517,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5527,9 +5527,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", @@ -5540,9 +5540,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "wasm-streams" @@ -5857,6 +5857,7 @@ dependencies = [ "manganis", "once_cell", "serde", + "strum", "surrealdb", "thiserror", "time", diff --git a/Cargo.toml b/Cargo.toml index 248d866..59f5105 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ time = { version = "0.3", optional = true } once_cell = { version = "1.19", optional = true } surrealdb = { version = "2.0", optional = true } thiserror = { version = "1.0" } +strum = { version = "0.26", features = ["derive"] } csv = { version = "1.3", optional = true } diff --git a/assets/tailwind.css b/assets/tailwind.css index 4122385..e7622de 100644 --- a/assets/tailwind.css +++ b/assets/tailwind.css @@ -1527,6 +1527,40 @@ html { align-items: center; } +.select { + display: inline-flex; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + height: 3rem; + min-height: 3rem; + padding-inline-start: 1rem; + padding-inline-end: 2.5rem; + font-size: 0.875rem; + line-height: 1.25rem; + line-height: 2; + border-radius: var(--rounded-btn, 0.5rem); + border-width: 1px; + border-color: transparent; + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); + background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), + linear-gradient(135deg, currentColor 50%, transparent 50%); + background-position: calc(100% - 20px) calc(1px + 50%), + calc(100% - 16.1px) calc(1px + 50%); + background-size: 4px 4px, + 4px 4px; + background-repeat: no-repeat; +} + +.select[multiple] { + height: auto; +} + .steps { display: inline-grid; grid-auto-flow: column; @@ -1651,6 +1685,23 @@ input.tab:checked + .tab-content, background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); } +.textarea { + min-height: 3rem; + flex-shrink: 1; + padding-left: 1rem; + padding-right: 1rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + font-size: 0.875rem; + line-height: 1.25rem; + line-height: 2; + border-radius: var(--rounded-btn, 0.5rem); + border-width: 1px; + border-color: transparent; + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); +} + .toggle { flex-shrink: 0; --tglbg: var(--fallback-b1,oklch(var(--b1)/1)); @@ -2475,6 +2526,54 @@ details.collapse summary::-webkit-details-marker { } } +.select-bordered { + border-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.select:focus { + box-shadow: none; + border-color: var(--fallback-bc,oklch(var(--bc)/0.2)); + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; + outline-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.select-disabled, + .select:disabled, + .select[disabled] { + cursor: not-allowed; + --tw-border-opacity: 1; + border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); + color: var(--fallback-bc,oklch(var(--bc)/0.4)); +} + +.select-disabled::-moz-placeholder, .select:disabled::-moz-placeholder, .select[disabled]::-moz-placeholder { + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); + --tw-placeholder-opacity: 0.2; +} + +.select-disabled::placeholder, + .select:disabled::placeholder, + .select[disabled]::placeholder { + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); + --tw-placeholder-opacity: 0.2; +} + +.select-multiple, + .select[multiple], + .select[size].select:not([size="1"]) { + background-image: none; + padding-right: 1rem; +} + +[dir="rtl"] .select { + background-position: calc(0% + 12px) calc(1px + 50%), + calc(0% + 16px) calc(1px + 50%); +} + @keyframes skeleton { from { background-position: 150%; @@ -2787,6 +2886,42 @@ details.collapse summary::-webkit-details-marker { border-top-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); } +.textarea-bordered { + border-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.textarea:focus { + box-shadow: none; + border-color: var(--fallback-bc,oklch(var(--bc)/0.2)); + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; + outline-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.textarea-disabled, + .textarea:disabled, + .textarea[disabled] { + cursor: not-allowed; + --tw-border-opacity: 1; + border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); + color: var(--fallback-bc,oklch(var(--bc)/0.4)); +} + +.textarea-disabled::-moz-placeholder, .textarea:disabled::-moz-placeholder, .textarea[disabled]::-moz-placeholder { + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); + --tw-placeholder-opacity: 0.2; +} + +.textarea-disabled::placeholder, + .textarea:disabled::placeholder, + .textarea[disabled]::placeholder { + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); + --tw-placeholder-opacity: 0.2; +} + @keyframes toast-pop { 0% { transform: scale(0.9); @@ -2872,6 +3007,14 @@ details.collapse summary::-webkit-details-marker { padding-right: 0.563rem; } +.btn-sm { + height: 2rem; + min-height: 2rem; + padding-left: 0.75rem; + padding-right: 0.75rem; + font-size: 0.875rem; +} + .btn-wide { width: 16rem; } @@ -2900,6 +3043,13 @@ details.collapse summary::-webkit-details-marker { padding: 0px; } +.btn-circle:where(.btn-sm) { + height: 2rem; + width: 2rem; + border-radius: 9999px; + padding: 0px; +} + .join.join-vertical { flex-direction: column; } @@ -3162,6 +3312,10 @@ details.collapse summary::-webkit-details-marker { margin-right: 0.75rem; } +.mr-auto { + margin-right: auto; +} + .mt-10 { margin-top: 2.5rem; } @@ -3334,6 +3488,10 @@ details.collapse summary::-webkit-details-marker { gap: 1.25rem; } +.gap-y-5 { + row-gap: 1.25rem; +} + .space-y-3 > :not([hidden]) ~ :not([hidden]) { --tw-space-y-reverse: 0; margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse))); diff --git a/devenv.lock b/devenv.lock index 868f058..c93b8b0 100644 --- a/devenv.lock +++ b/devenv.lock @@ -3,10 +3,10 @@ "devenv": { "locked": { "dir": "src/modules", - "lastModified": 1728113618, + "lastModified": 1732585607, "owner": "cachix", "repo": "devenv", - "rev": "a8495abab31ce52cd45d343caa760046c0c7ee74", + "rev": "a520f05c40ebecaf5e17064b27e28ba8e70c49fb", "type": "github" }, "original": { @@ -24,10 +24,10 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1728196311, + "lastModified": 1732602776, "owner": "nix-community", "repo": "fenix", - "rev": "26971356e387b5ec0578f52be1bbd82ecf6dbad4", + "rev": "e0d44b70dcd2b98dd77857b4c5c7b1dc6b1ef56d", "type": "github" }, "original": { @@ -73,10 +73,10 @@ }, "nixpkgs": { "locked": { - "lastModified": 1728067476, + "lastModified": 1731797254, "owner": "NixOS", "repo": "nixpkgs", - "rev": "6e6b3dd395c3b1eb9be9f2d096383a8d05add030", + "rev": "e8c38b73aeb218e27163376a2d617e61a2ad9b59", "type": "github" }, "original": { @@ -88,10 +88,10 @@ }, "nixpkgs-stable": { "locked": { - "lastModified": 1728067476, + "lastModified": 1731797254, "owner": "NixOS", "repo": "nixpkgs", - "rev": "6e6b3dd395c3b1eb9be9f2d096383a8d05add030", + "rev": "e8c38b73aeb218e27163376a2d617e61a2ad9b59", "type": "github" }, "original": { @@ -111,10 +111,10 @@ "nixpkgs-stable": "nixpkgs-stable" }, "locked": { - "lastModified": 1728092656, + "lastModified": 1732021966, "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "1211305a5b237771e13fcca0c51e60ad47326a9a", + "rev": "3308484d1a443fc5bc92012435d79e80458fe43c", "type": "github" }, "original": { @@ -134,10 +134,10 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1728064742, + "lastModified": 1732562640, "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "5982d9c420d0dc90739171829f0d2e9c80d98979", + "rev": "157c7d01149e9be7179c5724b89d8d073e923bd8", "type": "github" }, "original": { diff --git a/devenv.nix b/devenv.nix index 42b3cbe..5eb9837 100644 --- a/devenv.nix +++ b/devenv.nix @@ -40,7 +40,7 @@ # https://devenv.sh/processes/ processes.tailwind.exec = "watchexec -e rs ${lib.getExe pkgs.tailwindcss} -i input.css -o assets/tailwind.css"; - processes.dioxus.exec = "dx serve"; + # processes.dioxus.exec = "dx serve"; processes.surrealdb.exec = "docker compose up"; # See full reference at https://devenv.sh/reference/options/ diff --git a/src/components/news.rs b/src/components/news.rs index 3998e9e..7af4a22 100644 --- a/src/components/news.rs +++ b/src/components/news.rs @@ -1,5 +1,7 @@ use dioxus::prelude::*; +pub mod create; + #[component] pub fn News() -> Element { rsx! { diff --git a/src/components/news/create.rs b/src/components/news/create.rs new file mode 100644 index 0000000..b4c85b0 --- /dev/null +++ b/src/components/news/create.rs @@ -0,0 +1,170 @@ +use std::{collections::HashMap, ops::DerefMut}; + +use dioxus::prelude::*; + +#[component] +pub fn NewsCreate() -> Element { + let groups = use_context::(); + let hours = use_context::(); + + rsx! { + div { + class: "w-full max-w-2xl space-y-3", + h1 { class: "text-xl font-bold text-primary", "Nieuw bericht" } + form { + class: "flex flex-col gap-y-5", + label { + class: "form-control w-full", + div { + class: "label", + span { class: "label-text", "Titel" } + }, + input { + r#type: "text", + class: "input input-bordered w-full", + } + } + label { + class: "form-control w-full", + div { + class: "label", + span { class: "label-text", "Bericht" } + }, + textarea { + class: "textarea textarea-bordered w-full", + } + }, + TargetSelect { } + } + } + } +} + +#[derive(Eq, PartialEq, Clone, Copy, Debug)] +enum TargetKind { + None, + All, + Group, + Hourgroup, + Hour, + Member, + Account, + Day, +} + +impl std::fmt::Display for TargetKind { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + TargetKind::All => write!(f, "all"), + TargetKind::Group => write!(f, "group"), + TargetKind::Hourgroup => write!(f, "hourgroup"), + TargetKind::Hour => write!(f, "hour"), + TargetKind::Member => write!(f, "member"), + TargetKind::Account => write!(f, "account"), + TargetKind::Day => write!(f, "day"), + TargetKind::None => write!(f, "none"), + } + } +} + +struct Target { + kind: TargetKind, + value: String, +} + +impl TargetKind { + fn from_string(input: &str) -> Self { + match input { + "all" => Self::All, + "group" => Self::Group, + "hourgroup" => Self::Hourgroup, + "hour" => Self::Hour, + "member" => Self::Member, + "account" => Self::Account, + "day" => Self::Day, + _ => Self::None, + } + } +} + +#[component] +fn TargetSelect() -> Element { + let mut targets: Signal> = use_signal(|| { + HashMap::from([( + 0, + Target { + kind: TargetKind::None, + value: String::new(), + }, + )]) + }); + + let mut target_id = use_signal(|| 1); + + let target_ids = use_memo(move || { + let mut filtered_targets = targets.read().iter().map(|x| *x.0).collect::>(); + + filtered_targets.sort_unstable(); + + filtered_targets + }); + + rsx! { + label { + class: "form-control w-full", + div { + class: "label", + span { class: "label-text", "Naar" } + }, + for id in target_ids() { + TargetEntry { key: "{id}", id, targets } + } + button { + class: "btn btn-primary btn-outline btn-sm mt-3 mr-auto", + r#type: "button", + onclick: move |_| { + let id = target_id(); + let target = Target { + kind: TargetKind::None, + value: String::new(), + }; + targets.write().insert(id, target); + target_id += 1; + + }, + "Conditie toevoegen", + } + } + } +} + +#[component] +fn TargetEntry(mut targets: Signal>, id: u32) -> Element { + let kind = use_memo(move || targets.read().get(&id).unwrap().kind); + + rsx! { + div { + class: "join w-full mt-3", + select { + class: "select select-bordered join-item w-full", + oninput: move |event| { + let target_kind = TargetKind::from_string(&event.value()); + targets.write().get_mut(&id).unwrap().kind = target_kind; + }, + option { disabled: true, selected: true, value: "none", "Selecteer een type" }, + option { value: "all", selected: kind() == TargetKind::All, "Iedereen" } + option { value: "group", selected: kind() == TargetKind::Group, "Groep" } + option { value: "day", selected: kind() == TargetKind::Day, "Dag" } + option { value: "hour", selected: kind() == TargetKind::Hour, "Uur" } + option { value: "hourgroup", selected: kind() == TargetKind::Hourgroup, "Lesgroep" } + option { value: "member", selected: kind() == TargetKind::Member, "Lid" } + option { value: "account", selected: kind() == TargetKind::Account, "Account" } + } + match kind() { + _ => rsx! { + div { class: "input input-bordered w-full join-item" } + } + } + } + } +} diff --git a/src/main.rs b/src/main.rs index 86dd96e..54bef01 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,8 +4,6 @@ mod components; mod err; mod util; -use std::collections::HashMap; - #[cfg(feature = "server")] pub use err::Error; @@ -21,6 +19,7 @@ use components::agenda::Agenda; use components::home::Home; use components::layout::AdminLayout; use components::layout::Global; +use components::news::create::NewsCreate; use components::news::News; use components::settings::Settings; @@ -34,6 +33,8 @@ pub enum Route { Agenda {}, #[route("/news")] News {}, + #[route("/news/create")] + NewsCreate {}, #[route("/settings")] Settings {}, @@ -90,6 +91,9 @@ fn main() { #[derive(Clone)] pub struct Groups<'a>(Vec<(String, &'a str)>); +#[derive(Clone)] +pub struct Hours<'a>(Vec<(String, &'a str)>); + fn App() -> Element { let groups = Groups(vec![ ("bestuur".to_string(), "Bestuur"), @@ -99,7 +103,18 @@ fn App() -> Element { ("wedstrijden_trainer".to_string(), "Wedstrijden trainer"), ]); + let hours = Hours(vec![ + ("a".to_string(), "A"), + ("b".to_string(), "B"), + ("c".to_string(), "C"), + ("d".to_string(), "D"), + ("e".to_string(), "E"), + ("z".to_string(), "Z"), + ("wedstrijd".to_string(), "Wedstrijd"), + ]); + use_context_provider(|| groups); + use_context_provider(|| hours); rsx! { div {