Added function for csv to members

This commit is contained in:
xeovalyte 2024-07-19 17:50:50 +02:00
parent e418456a82
commit 32d03540fd
Signed by: xeovalyte
SSH Key Fingerprint: SHA256:kSQDrQDmKzljJzfGYcd3m9RqHi4h8rSwkZ3sQ9kBURo
6 changed files with 240 additions and 9 deletions

22
Cargo.lock generated
View File

@ -926,6 +926,27 @@ dependencies = [
"typenum", "typenum",
] ]
[[package]]
name = "csv"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe"
dependencies = [
"csv-core",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "csv-core"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "darling" name = "darling"
version = "0.20.10" version = "0.20.10"
@ -5831,6 +5852,7 @@ name = "wrbapp"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"axum", "axum",
"csv",
"dioxus", "dioxus",
"dioxus-logger", "dioxus-logger",
"manganis", "manganis",

View File

@ -16,6 +16,8 @@ axum = { version = "0.7.5", optional = true }
once_cell = { version = "1.19.0", optional = true } once_cell = { version = "1.19.0", optional = true }
surrealdb = { version = "1.5.4", features = ["kv-speedb"], optional = true } surrealdb = { version = "1.5.4", features = ["kv-speedb"], optional = true }
csv = { version = "1.3.0", optional = true }
# Debug # Debug
tracing = "0.1.40" tracing = "0.1.40"
dioxus-logger = "0.5.0" dioxus-logger = "0.5.0"
@ -23,5 +25,5 @@ manganis = "0.2.2"
[features] [features]
default = [] default = []
server = [ "dioxus/axum", "tokio", "axum", "once_cell", "surrealdb" ] server = [ "dioxus/axum", "tokio", "axum", "once_cell", "surrealdb", "csv" ]
web = ["dioxus/web"] web = ["dioxus/web"]

View File

@ -818,7 +818,48 @@ html {
content: var(--tw-content); content: var(--tw-content);
} }
.dropdown {
position: relative;
display: inline-block;
}
.dropdown > *:not(summary):focus {
outline: 2px solid transparent;
outline-offset: 2px;
}
.dropdown .dropdown-content {
position: absolute;
}
.dropdown:is(:not(details)) .dropdown-content {
visibility: hidden;
opacity: 0;
transform-origin: top;
--tw-scale-x: .95;
--tw-scale-y: .95;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
transition-duration: 200ms;
}
.dropdown.dropdown-open .dropdown-content,
.dropdown:not(.dropdown-hover):focus .dropdown-content,
.dropdown:focus-within .dropdown-content {
visibility: visible;
opacity: 1;
}
@media (hover: hover) { @media (hover: hover) {
.dropdown.dropdown-hover:hover .dropdown-content {
visibility: visible;
opacity: 1;
}
.btn:hover { .btn:hover {
--tw-border-opacity: 1; --tw-border-opacity: 1;
border-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-border-opacity))); border-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-border-opacity)));
@ -879,6 +920,16 @@ html {
border-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black); border-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black);
} }
} }
.dropdown.dropdown-hover:hover .dropdown-content {
--tw-scale-x: 1;
--tw-scale-y: 1;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
}
.dropdown:is(details) summary::-webkit-details-marker {
display: none;
} }
.input { .input {
@ -906,6 +957,12 @@ html {
margin-inline-end: -1rem; margin-inline-end: -1rem;
} }
.join .dropdown .join-item:first-child:not(:last-child),
.join *:first-child:not(:last-child) .dropdown .join-item {
border-start-end-radius: inherit;
border-end-end-radius: inherit;
}
.navbar { .navbar {
display: flex; display: flex;
align-items: center; align-items: center;
@ -1024,6 +1081,14 @@ html {
} }
} }
.dropdown.dropdown-open .dropdown-content,
.dropdown:focus .dropdown-content,
.dropdown:focus-within .dropdown-content {
--tw-scale-x: 1;
--tw-scale-y: 1;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.input input { .input input {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity))); background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)));

View File

@ -1 +1,2 @@
#[cfg(feature = "server")]
pub mod member; pub mod member;

View File

@ -1,15 +1,19 @@
use std::collections::BTreeSet;
mod migration; mod migration;
#[derive(Debug, PartialEq, Eq)]
pub struct Member { pub struct Member {
id: String, id: String,
name: Name, name: Name,
hours: Vec<String>, hours: BTreeSet<String>,
groups: Vec<String>, groups: BTreeSet<String>,
diploma: Option<String>, diploma: Option<String>,
registration_token: Option<String>, registration_token: Option<String>,
} }
#[derive(Debug, PartialEq, Eq)]
pub struct Name { pub struct Name {
first: String, first: String,
last: String, full: String,
} }

View File

@ -1,6 +1,7 @@
use super::Member; use super::Member;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::collections::HashMap; use std::collections::{BTreeSet, HashMap, HashSet};
// Create a store for saving information when migrating to a new members list // Create a store for saving information when migrating to a new members list
static MEMBERS_STORE: Lazy<MembersStore> = Lazy::new(|| MembersStore::new()); static MEMBERS_STORE: Lazy<MembersStore> = Lazy::new(|| MembersStore::new());
@ -33,10 +34,146 @@ impl MembersStore {
} }
} }
impl Member { // Create a row for the csv file
async fn migrate() {} #[derive(Debug, serde::Deserialize)]
struct Row {
#[serde(rename = "Relatiecode")]
id: String,
#[serde(rename = "Roepnaam")]
first_name: String,
#[serde(rename = "Tussenvoegsel(s)")]
middle_name: String,
#[serde(rename = "Achternaam")]
last_name: String,
#[serde(rename = "E-mail")]
email: String,
#[serde(rename = "Verenigingssporten")]
hours: String,
#[serde(rename = "Diploma dropdown 1")]
diploma: Option<String>,
} }
fn csv_to_vec(input: String) -> Result<Vec<Member>, ()> { impl Row {
Ok(vec![]) fn to_member(&self) -> Member {
Member {
id: self.id.trim().to_string(),
name: super::Name {
first: self.first_name.clone(),
full: self.generate_full_name(),
},
hours: self.generate_hours(),
groups: BTreeSet::new(),
diploma: self.diploma.clone(),
registration_token: None,
}
}
// Get the hour data from the raw string
fn generate_hours(&self) -> BTreeSet<String> {
let mut hours: BTreeSet<String> = BTreeSet::new();
let group_parts: Vec<&str> = self.hours.split(", ").collect();
for group in group_parts {
let hour_parts: Vec<&str> = group.split(" - ").collect();
for part in hour_parts {
if &*part != "Groep" {
hours.insert(part.to_string());
}
}
}
hours
}
// Generate the full name from 3 parts
fn generate_full_name(&self) -> String {
let mut parts: Vec<&str> = vec![];
parts.push(&self.first_name);
parts.push(&self.middle_name);
parts.push(&self.last_name);
let filtered_strings: Vec<&str> = parts.into_iter().filter(|s| !s.is_empty()).collect();
filtered_strings.join(" ")
}
}
// Convert the raw csv file to rust objects
fn csv_to_rows(input: String) -> Result<Vec<Row>, Box<dyn std::error::Error>> {
let mut members: Vec<Row> = vec![];
let mut rdr = csv::Reader::from_reader(input.as_bytes());
for result in rdr.deserialize() {
let row: Row = result?;
members.push(row);
}
Ok(members)
}
// Covert the rows to formatted members
fn rows_to_members(rows: Vec<Row>) -> Vec<Member> {
let mut members: Vec<Member> = vec![];
for row in rows {
members.push(row.to_member());
}
members
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn csv_to_members_test() -> Result<(), String> {
let data = "Relatiecode,Volledige naam (1),Roepnaam,Tussenvoegsel(s),Achternaam,E-mail,2e E-mail,Verenigingssporten,Diploma dropdown 1
D000001,\"Last, First\",First,,Last,first.last@example.com,first.last@example.nl,\"Groep - Wedstrijd - Zaterdag, Groep - Z5 - Zaterdag\",LS1
D000002,\"Last2, First2\",First2,,Last2,first1.last@example.nl,,Groep - Z5 - Zaterdag,ZR4".to_string();
let expected = vec![
Member {
id: "D000001".to_string(),
name: super::super::Name {
first: "First".to_string(),
full: "First Last".to_string(),
},
hours: BTreeSet::from([
"Wedstrijd".to_string(),
"Z5".to_string(),
"Zaterdag".to_string(),
]),
groups: BTreeSet::new(),
diploma: Some("LS1".to_string()),
registration_token: None,
},
Member {
id: "D000002".to_string(),
name: super::super::Name {
first: "First2".to_string(),
full: "First2 Last2".to_string(),
},
hours: BTreeSet::from(["Z5".to_string(), "Zaterdag".to_string()]),
groups: BTreeSet::new(),
diploma: Some("ZR4".to_string()),
registration_token: None,
},
];
let rows = match csv_to_rows(data) {
Ok(r) => r,
Err(err) => return Err(err.to_string()),
};
let members = rows_to_members(rows);
assert_eq!(expected, members);
Ok(())
}
} }