Added verify step
This commit is contained in:
parent
7ea256f85b
commit
0f0aff81f4
@ -754,6 +754,14 @@ html {
|
||||
--tw-contain-style: ;
|
||||
}
|
||||
|
||||
@media (hover:hover) {
|
||||
.table tr.hover:hover,
|
||||
.table tr.hover:nth-child(even):hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
height: 3rem;
|
||||
@ -818,6 +826,57 @@ html {
|
||||
content: var(--tw-content);
|
||||
}
|
||||
|
||||
.card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: var(--rounded-box, 1rem);
|
||||
}
|
||||
|
||||
.card:focus {
|
||||
outline: 2px solid transparent;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.card figure {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.card.image-full {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.card.image-full:before {
|
||||
position: relative;
|
||||
content: "";
|
||||
z-index: 10;
|
||||
border-radius: var(--rounded-box, 1rem);
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.card.image-full:before,
|
||||
.card.image-full > * {
|
||||
grid-column-start: 1;
|
||||
grid-row-start: 1;
|
||||
}
|
||||
|
||||
.card.image-full > figure img {
|
||||
height: 100%;
|
||||
-o-object-fit: cover;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.card.image-full > .card-body {
|
||||
position: relative;
|
||||
z-index: 20;
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)));
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
@ -1137,6 +1196,39 @@ html {
|
||||
min-width: 4rem;
|
||||
}
|
||||
|
||||
.table {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
border-radius: var(--rounded-box, 1rem);
|
||||
text-align: left;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
|
||||
.table :where(.table-pin-rows thead tr) {
|
||||
position: sticky;
|
||||
top: 0px;
|
||||
z-index: 1;
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
|
||||
}
|
||||
|
||||
.table :where(.table-pin-rows tfoot tr) {
|
||||
position: sticky;
|
||||
bottom: 0px;
|
||||
z-index: 1;
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
|
||||
}
|
||||
|
||||
.table :where(.table-pin-cols tr th) {
|
||||
position: sticky;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
|
||||
}
|
||||
|
||||
.btm-nav > *.disabled,
|
||||
.btm-nav > *[disabled] {
|
||||
pointer-events: none;
|
||||
@ -1271,6 +1363,53 @@ html {
|
||||
}
|
||||
}
|
||||
|
||||
.card :where(figure:first-child) {
|
||||
overflow: hidden;
|
||||
border-start-start-radius: inherit;
|
||||
border-start-end-radius: inherit;
|
||||
border-end-start-radius: unset;
|
||||
border-end-end-radius: unset;
|
||||
}
|
||||
|
||||
.card :where(figure:last-child) {
|
||||
overflow: hidden;
|
||||
border-start-start-radius: unset;
|
||||
border-start-end-radius: unset;
|
||||
border-end-start-radius: inherit;
|
||||
border-end-end-radius: inherit;
|
||||
}
|
||||
|
||||
.card:focus-visible {
|
||||
outline: 2px solid currentColor;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.card.bordered {
|
||||
border-width: 1px;
|
||||
--tw-border-opacity: 1;
|
||||
border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));
|
||||
}
|
||||
|
||||
.card.compact .card-body {
|
||||
padding: 1rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.75rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.card.image-full :where(figure) {
|
||||
overflow: hidden;
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
@keyframes checkmark {
|
||||
0% {
|
||||
background-position-y: 5px;
|
||||
@ -1643,6 +1782,45 @@ html {
|
||||
color: var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity)));
|
||||
}
|
||||
|
||||
.table:where([dir="rtl"], [dir="rtl"] *) {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.table :where(th, td) {
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
padding-top: 0.75rem;
|
||||
padding-bottom: 0.75rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.table tr.active,
|
||||
.table tr.active:nth-child(even),
|
||||
.table-zebra tbody tr:nth-child(even) {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
|
||||
}
|
||||
|
||||
.table :where(thead tr, tbody tr:not(:last-child), tbody tr:first-child:last-child) {
|
||||
border-bottom-width: 1px;
|
||||
--tw-border-opacity: 1;
|
||||
border-bottom-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));
|
||||
}
|
||||
|
||||
.table :where(thead, tfoot) {
|
||||
white-space: nowrap;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1rem;
|
||||
font-weight: 700;
|
||||
color: var(--fallback-bc,oklch(var(--bc)/0.6));
|
||||
}
|
||||
|
||||
.table :where(tfoot) {
|
||||
border-top-width: 1px;
|
||||
--tw-border-opacity: 1;
|
||||
border-top-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));
|
||||
}
|
||||
|
||||
@keyframes toast-pop {
|
||||
0% {
|
||||
transform: scale(0.9);
|
||||
@ -1737,6 +1915,14 @@ html {
|
||||
grid-template-rows: repeat(1, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.card-compact .card-title {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.card-normal .card-title {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.join.join-vertical > :where(*:not(:first-child)) {
|
||||
margin-left: 0px;
|
||||
margin-right: 0px;
|
||||
@ -1809,8 +1995,12 @@ html {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.mt-16 {
|
||||
margin-top: 4rem;
|
||||
.mb-1 {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.mb-5 {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.mt-5 {
|
||||
@ -1821,6 +2011,10 @@ html {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.table {
|
||||
display: table;
|
||||
}
|
||||
|
||||
.contents {
|
||||
display: contents;
|
||||
}
|
||||
@ -1830,6 +2024,10 @@ html {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.h-\[30rem\] {
|
||||
height: 30rem;
|
||||
}
|
||||
|
||||
.h-full {
|
||||
height: 100%;
|
||||
}
|
||||
@ -1847,6 +2045,10 @@ html {
|
||||
min-height: 4rem;
|
||||
}
|
||||
|
||||
.w-96 {
|
||||
width: 24rem;
|
||||
}
|
||||
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
@ -1867,6 +2069,10 @@ html {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.flex-wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.flex-nowrap {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
@ -1891,6 +2097,14 @@ html {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.gap-5 {
|
||||
gap: 1.25rem;
|
||||
}
|
||||
|
||||
.overflow-auto {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.overflow-y-auto {
|
||||
overflow-y: auto;
|
||||
}
|
||||
@ -1900,6 +2114,10 @@ html {
|
||||
background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
|
||||
}
|
||||
|
||||
.p-5 {
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.px-2 {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
@ -1920,8 +2138,8 @@ html {
|
||||
padding-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.pb-36 {
|
||||
padding-bottom: 9rem;
|
||||
.pb-10 {
|
||||
padding-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.text-xl {
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::util::model::member::{Member, MembersMigration};
|
||||
use dioxus::prelude::{dioxus_elements::FileEngine, *};
|
||||
use std::sync::Arc;
|
||||
|
||||
@ -17,12 +18,13 @@ enum Steps {
|
||||
#[component]
|
||||
pub fn Migration() -> Element {
|
||||
let step = use_signal(|| Steps::Upload);
|
||||
let members_migration = use_signal(|| MembersMigration::new());
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
class: "flex flex-col items-center justify-center h-full py-10",
|
||||
class: "flex flex-col items-center justify-center py-10",
|
||||
ul {
|
||||
class: "steps pb-36",
|
||||
class: "steps pb-10",
|
||||
li { class: "step step-primary", "Uploaden" },
|
||||
li {
|
||||
class: "step",
|
||||
@ -32,16 +34,16 @@ pub fn Migration() -> Element {
|
||||
li { class: "step", "Klaar" },
|
||||
},
|
||||
match *step.read() {
|
||||
Steps::Upload => rsx! { Upload { step: step } },
|
||||
Steps::Verify => rsx! { Verify {} },
|
||||
Steps::Done => rsx! { Verify {} },
|
||||
Steps::Upload => rsx! { Upload { step: step, members_migration: members_migration } },
|
||||
Steps::Verify => rsx! { Verify { members_migration: members_migration} },
|
||||
Steps::Done => rsx! { Verify { members_migration: members_migration } },
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Upload(step: Signal<Steps>) -> Element {
|
||||
fn Upload(step: Signal<Steps>, members_migration: Signal<MembersMigration>) -> Element {
|
||||
let mut file_uploaded = use_signal(|| None);
|
||||
|
||||
let mut loading = use_signal(|| false);
|
||||
@ -69,23 +71,25 @@ fn Upload(step: Signal<Steps>) -> Element {
|
||||
Some(file) => {
|
||||
loading.set(true);
|
||||
|
||||
if let Ok(_response) = upload_members_list(file.contents.clone()).await {
|
||||
tracing::info!("Done");
|
||||
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", "Selecteer het ledenbestand" },
|
||||
h2 { class: "text-xl mb-5", "Selecteer de ledenlijst" },
|
||||
input {
|
||||
r#type: "file",
|
||||
class: "file-input file-input-bordered w-full mt-16",
|
||||
class: "file-input file-input-bordered w-full",
|
||||
accept: ".csv",
|
||||
multiple: false,
|
||||
autocomplete: "off",
|
||||
@ -99,32 +103,81 @@ fn Upload(step: Signal<Steps>) -> Element {
|
||||
}
|
||||
"Uploaden"
|
||||
}
|
||||
"{file_uploaded.read().is_none()}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Verify() -> Element {
|
||||
fn Verify(members_migration: Signal<MembersMigration>) -> Element {
|
||||
let mut loading = use_signal(|| false);
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
class: "flex flex-col items-center w-full h-full max-w-md mx-auto px-2",
|
||||
h2 { class: "text-xl", "Controleer de actie" },
|
||||
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().inserted.clone() }
|
||||
}
|
||||
div {
|
||||
class: "card bg-base-200 p-5",
|
||||
h2 { class: "card-title mb-1", "Verwijderen" }
|
||||
MembersTable { members: members_migration.read().removed.clone() }
|
||||
}
|
||||
div {
|
||||
class: "card bg-base-200 p-5",
|
||||
h2 { class: "card-title mb-1", "Updaten" }
|
||||
MembersTable { members: members_migration.read().updated.clone() }
|
||||
}
|
||||
}
|
||||
button {
|
||||
class: "btn btn-primary btn-wide mt-5",
|
||||
disabled: loading(),
|
||||
if loading() {
|
||||
span { class: "loading loading-spinner" }
|
||||
}
|
||||
"Toepassen"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn MembersTable(members: Vec<Member>) -> 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<String, ServerFnError> {
|
||||
use crate::util::model::member::Member;
|
||||
|
||||
tracing::info!("Getting members...");
|
||||
|
||||
let result = Member::migrate_proposal(input).await;
|
||||
|
||||
tracing::info!("{:?}", result);
|
||||
|
||||
Ok("Whoo".to_string())
|
||||
async fn upload_members_list(input: String) -> Result<MembersMigration, ServerFnError> {
|
||||
match Member::migrate_proposal(input).await {
|
||||
Ok(r) => Ok(r),
|
||||
Err(err) => Err(ServerFnError::new(err.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
#[server]
|
||||
|
@ -1,2 +1 @@
|
||||
#[cfg(feature = "server")]
|
||||
pub mod member;
|
||||
|
@ -1,24 +1,32 @@
|
||||
#[cfg(feature = "server")]
|
||||
use crate::util::surrealdb::DB;
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
mod migration;
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
|
||||
pub struct Member {
|
||||
id: String,
|
||||
name: Name,
|
||||
hours: BTreeSet<String>,
|
||||
groups: BTreeSet<String>,
|
||||
diploma: Option<String>,
|
||||
registration_token: Option<String>,
|
||||
pub id: String,
|
||||
pub name: Name,
|
||||
pub hours: BTreeSet<String>,
|
||||
pub groups: BTreeSet<String>,
|
||||
pub diploma: Option<String>,
|
||||
pub registration_token: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
|
||||
pub struct Name {
|
||||
first: String,
|
||||
full: String,
|
||||
pub first: String,
|
||||
pub full: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)]
|
||||
pub struct MembersMigration {
|
||||
pub inserted: Vec<Member>,
|
||||
pub updated: Vec<Member>,
|
||||
pub removed: Vec<Member>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
@ -31,3 +39,13 @@ impl Member {
|
||||
Ok(members)
|
||||
}
|
||||
}
|
||||
|
||||
impl MembersMigration {
|
||||
pub fn new() -> MembersMigration {
|
||||
MembersMigration {
|
||||
inserted: vec![],
|
||||
updated: vec![],
|
||||
removed: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
use crate::util::surrealdb::DB;
|
||||
|
||||
use super::Member;
|
||||
use super::{Member, MembersMigration};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::{BTreeSet, HashMap, HashSet};
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
|
||||
// Create a store for saving information when migrating to a new members list
|
||||
static MEMBERS_STORE: Lazy<MembersStore> = Lazy::new(|| MembersStore::new());
|
||||
@ -129,11 +127,10 @@ fn rows_to_members(rows: Vec<Row>) -> Vec<Member> {
|
||||
}
|
||||
|
||||
// Compare the new members list with the current
|
||||
// (inserted, updated, removed)
|
||||
fn combine_members_lists(
|
||||
new_members_list: Vec<Member>,
|
||||
current_members_list: Vec<Member>,
|
||||
) -> (Vec<Member>, Vec<Member>, Vec<Member>) {
|
||||
) -> MembersMigration {
|
||||
tracing::info!(
|
||||
"Current: {}, New: {}",
|
||||
current_members_list.len(),
|
||||
@ -183,13 +180,17 @@ fn combine_members_lists(
|
||||
}
|
||||
}
|
||||
|
||||
(inserted_members, updated_members, removed_members)
|
||||
MembersMigration {
|
||||
inserted: inserted_members,
|
||||
updated: updated_members,
|
||||
removed: removed_members,
|
||||
}
|
||||
}
|
||||
|
||||
impl Member {
|
||||
pub async fn migrate_proposal(
|
||||
csv: String,
|
||||
) -> Result<(Vec<Member>, Vec<Member>, Vec<Member>), Box<dyn std::error::Error>> {
|
||||
) -> Result<MembersMigration, Box<dyn std::error::Error>> {
|
||||
let rows = csv_to_rows(csv)?;
|
||||
|
||||
let new_members = rows_to_members(rows);
|
||||
|
Loading…
Reference in New Issue
Block a user