Added function for csv to members
This commit is contained in:
parent
e418456a82
commit
32d03540fd
22
Cargo.lock
generated
22
Cargo.lock
generated
@ -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",
|
||||||
|
@ -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"]
|
||||||
|
@ -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)));
|
||||||
|
@ -1 +1,2 @@
|
|||||||
|
#[cfg(feature = "server")]
|
||||||
pub mod member;
|
pub mod member;
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
@ -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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user