diff --git a/src/components/admin/members.rs b/src/components/admin/members.rs index 10d1891..a571eaa 100644 --- a/src/components/admin/members.rs +++ b/src/components/admin/members.rs @@ -1,231 +1 @@ -use crate::util::model::member::{Member, MembersMigration}; -use dioxus::prelude::{dioxus_elements::FileEngine, *}; -use manganis::ImageAsset; -use std::sync::Arc; - -#[derive(Debug)] -struct UploadedFile { - name: String, - contents: String, -} - -#[derive(PartialEq)] -enum Steps { - Upload, - Verify, - Done, -} - -#[component] -pub fn Migration() -> Element { - let step = use_signal(|| Steps::Upload); - let members_migration = use_signal(|| (0, MembersMigration::new())); - - rsx! { - div { - class: "flex flex-col items-center justify-center py-10", - ul { - class: "steps pb-10", - li { class: "step step-primary", "Uploaden" }, - li { - class: "step", - class: if let Steps::Verify | Steps::Done = *step.read() { { "step-primary" } }, - "Controleren" - }, - li { - class: "step", - class: if let Steps::Done = *step.read() { { "step-primary" } }, - "Klaar" - }, - }, - match *step.read() { - Steps::Upload => rsx! { Upload { step: step, members_migration: members_migration } }, - Steps::Verify => rsx! { Verify { step: step, members_migration: members_migration } }, - Steps::Done => rsx! { Done {} }, - } - } - } -} - -#[component] -fn Upload(step: Signal, members_migration: Signal<(u16, MembersMigration)>) -> Element { - let mut file_uploaded = use_signal(|| None); - - let mut loading = use_signal(|| false); - - let read_files = move |file_engine: Arc| async move { - let files = file_engine.files(); - for file_name in &files { - if let Some(contents) = file_engine.read_file_to_string(file_name).await { - file_uploaded.set(Some(UploadedFile { - name: file_name.clone(), - contents, - })); - } - } - }; - - let upload_files = move |evt: FormEvent| async move { - if let Some(file_engine) = evt.files() { - read_files(file_engine).await; - } - }; - - let sumbit = move |_evt: FormEvent| async move { - match &*file_uploaded.read() { - Some(file) => { - loading.set(true); - - if let Ok(response) = upload_members_list(file.contents.clone()).await { - members_migration.set(response); - step.set(Steps::Verify); - loading.set(false); - } - loading.set(false); - } - None => tracing::info!("File doesn't exists"), - } - }; - - rsx! { - form { - class: "flex flex-col items-center w-full h-full max-w-md mx-auto px-2", - onsubmit: sumbit, - h2 { class: "text-xl mb-5", "Selecteer de ledenlijst" }, - input { - r#type: "file", - class: "file-input file-input-bordered w-full", - accept: ".csv", - multiple: false, - autocomplete: "off", - onchange: upload_files, - } - button { - class: "btn btn-primary btn-wide mt-5", - disabled: file_uploaded.read().is_none() || loading(), - if loading() { - span { class: "loading loading-spinner" } - } - "Uploaden" - } - } - } -} - -#[component] -fn Verify(step: Signal, members_migration: Signal<(u16, MembersMigration)>) -> Element { - let mut loading = use_signal(|| false); - - let submit_accept = move |_| async move { - loading.set(true); - - if let Ok(_response) = migration_response(true, members_migration.read().0).await { - step.set(Steps::Done); - loading.set(false); - }; - loading.set(false); - }; - - rsx! { - div { - class: "flex flex-col items-center justify-center w-full mx-auto px-2", - h2 { class: "text-xl mb-5", "Controleer de verandering" }, - div { - class: "flex flex-wrap gap-5", - div { - class: "card bg-base-200 p-5", - h2 { class: "card-title mb-1", "Toevoegen" } - MembersTable { members: members_migration.read().1.inserted.clone() } - } - div { - class: "card bg-base-200 p-5", - h2 { class: "card-title mb-1", "Verwijderen" } - MembersTable { members: members_migration.read().1.removed.clone() } - } - div { - class: "card bg-base-200 p-5", - h2 { class: "card-title mb-1", "Updaten" } - MembersTable { members: members_migration.read().1.updated.clone() } - } - } - button { - class: "btn btn-primary btn-wide mt-5", - onclick: submit_accept, - disabled: loading(), - if loading() { - span { class: "loading loading-spinner" } - } - "Toepassen" - } - } - } -} - -#[component] -fn Done() -> Element { - rsx! { - div { - class: "flex flex-col items-center justify-center w-full mx-auto px-2", - h2 { class: "text-xl mb-5", "Ledenlijst is geüpdate" }, - div { - class: "w-80 mt-10", - img { - src: "/gifs/nick.webp", - } - } - button { - class: "btn btn-primary btn-wide mt-5", - "Terug naar Start" - } - } - } -} - -#[component] -fn MembersTable(members: Vec) -> Element { - rsx! { - div { - class: "h-[30rem] w-96 overflow-auto font-normal", - table { - class: "table table-pin-rows", - thead { - tr { - th { "Relatiecode" } - th { "Naam" } - } - } - tbody { - for member in members { - tr { - th { "{member.id}" } - th { "{member.name.full}" } - } - } - } - } - - } - } -} - -#[server] -async fn upload_members_list(input: String) -> Result<(u16, MembersMigration), ServerFnError> { - match Member::migrate_proposal(input).await { - Ok(r) => Ok(r), - Err(err) => Err(ServerFnError::new(err.to_string())), - } -} - -#[server] -async fn migration_response(correct: bool, id: u16) -> Result<(), ServerFnError> { - if correct { - match Member::migrate(id).await { - Err(err) => Err(ServerFnError::new(err.to_string())), - Ok(_) => Ok(()), - }?; - } else { - tracing::info!("Migrations denied"); - } - - Ok(()) -} +pub mod migration; diff --git a/src/components/admin/members/migration.rs b/src/components/admin/members/migration.rs new file mode 100644 index 0000000..3aea920 --- /dev/null +++ b/src/components/admin/members/migration.rs @@ -0,0 +1,230 @@ +use crate::util::model::member::{Member, MembersMigration}; +use dioxus::prelude::{dioxus_elements::FileEngine, *}; +use std::sync::Arc; + +#[derive(Debug)] +struct UploadedFile { + name: String, + contents: String, +} + +#[derive(PartialEq)] +enum Steps { + Upload, + Verify, + Done, +} + +#[component] +pub fn Migration() -> Element { + let step = use_signal(|| Steps::Upload); + let members_migration = use_signal(|| (0, MembersMigration::new())); + + rsx! { + div { + class: "flex flex-col items-center justify-center py-10", + ul { + class: "steps pb-10", + li { class: "step step-primary", "Uploaden" }, + li { + class: "step", + class: if let Steps::Verify | Steps::Done = *step.read() { { "step-primary" } }, + "Controleren" + }, + li { + class: "step", + class: if let Steps::Done = *step.read() { { "step-primary" } }, + "Klaar" + }, + }, + match *step.read() { + Steps::Upload => rsx! { Upload { step: step, members_migration: members_migration } }, + Steps::Verify => rsx! { Verify { step: step, members_migration: members_migration } }, + Steps::Done => rsx! { Done {} }, + } + } + } +} + +#[component] +fn Upload(step: Signal, members_migration: Signal<(u16, MembersMigration)>) -> Element { + let mut file_uploaded = use_signal(|| None); + + let mut loading = use_signal(|| false); + + let read_files = move |file_engine: Arc| async move { + let files = file_engine.files(); + for file_name in &files { + if let Some(contents) = file_engine.read_file_to_string(file_name).await { + file_uploaded.set(Some(UploadedFile { + name: file_name.clone(), + contents, + })); + } + } + }; + + let upload_files = move |evt: FormEvent| async move { + if let Some(file_engine) = evt.files() { + read_files(file_engine).await; + } + }; + + let sumbit = move |_evt: FormEvent| async move { + match &*file_uploaded.read() { + Some(file) => { + loading.set(true); + + if let Ok(response) = upload_members_list(file.contents.clone()).await { + members_migration.set(response); + step.set(Steps::Verify); + loading.set(false); + } + loading.set(false); + } + None => tracing::info!("File doesn't exists"), + } + }; + + rsx! { + form { + class: "flex flex-col items-center w-full h-full max-w-md mx-auto px-2", + onsubmit: sumbit, + h2 { class: "text-xl mb-5", "Selecteer de ledenlijst" }, + input { + r#type: "file", + class: "file-input file-input-bordered w-full", + accept: ".csv", + multiple: false, + autocomplete: "off", + onchange: upload_files, + } + button { + class: "btn btn-primary btn-wide mt-5", + disabled: file_uploaded.read().is_none() || loading(), + if loading() { + span { class: "loading loading-spinner" } + } + "Uploaden" + } + } + } +} + +#[component] +fn Verify(step: Signal, members_migration: Signal<(u16, MembersMigration)>) -> Element { + let mut loading = use_signal(|| false); + + let submit_accept = move |_| async move { + loading.set(true); + + if let Ok(_response) = migration_response(true, members_migration.read().0).await { + step.set(Steps::Done); + loading.set(false); + }; + loading.set(false); + }; + + rsx! { + div { + class: "flex flex-col items-center justify-center w-full mx-auto px-2", + h2 { class: "text-xl mb-5", "Controleer de verandering" }, + div { + class: "flex flex-wrap gap-5", + div { + class: "card bg-base-200 p-5", + h2 { class: "card-title mb-1", "Toevoegen" } + MembersTable { members: members_migration.read().1.inserted.clone() } + } + div { + class: "card bg-base-200 p-5", + h2 { class: "card-title mb-1", "Verwijderen" } + MembersTable { members: members_migration.read().1.removed.clone() } + } + div { + class: "card bg-base-200 p-5", + h2 { class: "card-title mb-1", "Updaten" } + MembersTable { members: members_migration.read().1.updated.clone() } + } + } + button { + class: "btn btn-primary btn-wide mt-5", + onclick: submit_accept, + disabled: loading(), + if loading() { + span { class: "loading loading-spinner" } + } + "Toepassen" + } + } + } +} + +#[component] +fn Done() -> Element { + rsx! { + div { + class: "flex flex-col items-center justify-center w-full mx-auto px-2", + h2 { class: "text-xl mb-5", "Ledenlijst is geüpdate" }, + div { + class: "w-80 mt-10", + img { + src: "/gifs/nick.webp", + } + } + button { + class: "btn btn-primary btn-wide mt-5", + "Terug naar Start" + } + } + } +} + +#[component] +fn MembersTable(members: Vec) -> Element { + rsx! { + div { + class: "h-[30rem] w-96 overflow-auto font-normal", + table { + class: "table table-pin-rows", + thead { + tr { + th { "Relatiecode" } + th { "Naam" } + } + } + tbody { + for member in members { + tr { + th { "{member.id}" } + th { "{member.name.full}" } + } + } + } + } + + } + } +} + +#[server] +async fn upload_members_list(input: String) -> Result<(u16, MembersMigration), ServerFnError> { + match MembersMigration::migrate_proposal(input).await { + Ok(r) => Ok(r), + Err(err) => Err(ServerFnError::new(err.to_string())), + } +} + +#[server] +async fn migration_response(correct: bool, id: u16) -> Result<(), ServerFnError> { + if correct { + match MembersMigration::migrate(id).await { + Err(err) => Err(ServerFnError::new(err.to_string())), + Ok(_) => Ok(()), + }?; + } else { + tracing::info!("Migrations denied"); + } + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index e551fb6..aae0252 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ use dioxus::prelude::*; use tracing::{info, Level}; // Use routes -use components::admin::members::Migration; +use components::admin::members::migration::Migration; use components::home::Home; #[derive(Clone, Routable, Debug, PartialEq, serde::Serialize, serde::Deserialize)] diff --git a/src/util/model/member/migration.rs b/src/util/model/member/migration.rs index 3ed0a23..1507455 100644 --- a/src/util/model/member/migration.rs +++ b/src/util/model/member/migration.rs @@ -104,103 +104,103 @@ impl Row { } } -// Convert the raw csv file to rust objects -fn csv_to_rows(input: String) -> Result, Box> { - let mut members: Vec = vec![]; +impl MembersMigration { + // Convert the raw csv file to rust objects + fn csv_to_rows(input: String) -> Result, Box> { + let mut members: Vec = vec![]; - let mut rdr = csv::Reader::from_reader(input.as_bytes()); + let mut rdr = csv::Reader::from_reader(input.as_bytes()); - for result in rdr.deserialize() { - let row: Row = result?; - members.push(row); + for result in rdr.deserialize() { + let row: Row = result?; + members.push(row); + } + + Ok(members) } - Ok(members) -} + // Covert the rows to formatted members + fn rows_to_members(rows: Vec) -> Vec { + let mut members: Vec = vec![]; -// Covert the rows to formatted members -fn rows_to_members(rows: Vec) -> Vec { - let mut members: Vec = vec![]; + for row in rows { + members.push(row.to_member()); + } - for row in rows { - members.push(row.to_member()); + members } - members -} + // Compare the new members list with the current + fn combine_members_lists( + new_members_list: Vec, + current_members_list: Vec, + ) -> MembersMigration { + tracing::info!( + "Current: {}, New: {}", + current_members_list.len(), + new_members_list.len() + ); -// Compare the new members list with the current -fn combine_members_lists( - new_members_list: Vec, - current_members_list: Vec, -) -> MembersMigration { - tracing::info!( - "Current: {}, New: {}", - current_members_list.len(), - new_members_list.len() - ); + let current_members_map: HashMap = current_members_list + .clone() + .into_iter() + .map(|m| (m.id.clone(), m)) + .collect(); - let current_members_map: HashMap = current_members_list - .clone() - .into_iter() - .map(|m| (m.id.clone(), m)) - .collect(); + let new_members_map: HashMap = new_members_list + .clone() + .into_iter() + .map(|m| (m.id.clone(), m)) + .collect(); - let new_members_map: HashMap = new_members_list - .clone() - .into_iter() - .map(|m| (m.id.clone(), m)) - .collect(); + let mut inserted_members: Vec = vec![]; + let mut updated_members: Vec = vec![]; + let mut removed_members: Vec = vec![]; - let mut inserted_members: Vec = vec![]; - let mut updated_members: Vec = vec![]; - let mut removed_members: Vec = vec![]; + for current_member in current_members_list { + if let Some(new_member) = new_members_map.get(¤t_member.id) { + // Update existing member + let new_member_clone = new_member.clone(); - for current_member in current_members_list { - if let Some(new_member) = new_members_map.get(¤t_member.id) { - // Update existing member - let new_member_clone = new_member.clone(); + updated_members.push(Member { + id: current_member.id, + name: new_member_clone.name, + hours: new_member_clone.hours, + groups: current_member.groups, + diploma: new_member_clone.diploma, + registration_token: current_member.diploma, + }) + } else { + // Remove member + removed_members.push(current_member); + } + } - updated_members.push(Member { - id: current_member.id, - name: new_member_clone.name, - hours: new_member_clone.hours, - groups: current_member.groups, - diploma: new_member_clone.diploma, - registration_token: current_member.diploma, - }) - } else { - // Remove member - removed_members.push(current_member); + for new_member in new_members_list { + // Insert new member + // TODO Generate registration token + if !current_members_map.contains_key(&new_member.id) { + inserted_members.push(new_member); + } + } + + Self { + inserted: inserted_members, + updated: updated_members, + removed: removed_members, } } - for new_member in new_members_list { - // Insert new member - // TODO Generate registration token - if !current_members_map.contains_key(&new_member.id) { - inserted_members.push(new_member); - } - } - - MembersMigration { - inserted: inserted_members, - updated: updated_members, - removed: removed_members, - } -} - -impl Member { pub async fn migrate_proposal( csv: String, ) -> Result<(u16, MembersMigration), Box> { - let rows = csv_to_rows(csv)?; + let rows = Self::csv_to_rows(csv)?; - let new_members = rows_to_members(rows); + let new_members = Self::rows_to_members(rows); let current_members = Member::fetch_all().await?; - let members_migration = combine_members_lists(new_members, current_members); + let members_migration = Self::combine_members_lists(new_members, current_members); let count = MEMBERS_STORE.lock().await.insert(members_migration.clone());