Added verify step

This commit is contained in:
xeovalyte 2024-07-23 20:42:00 +02:00
parent 7ea256f85b
commit 0f0aff81f4
Signed by: xeovalyte
SSH Key Fingerprint: SHA256:kSQDrQDmKzljJzfGYcd3m9RqHi4h8rSwkZ3sQ9kBURo
5 changed files with 337 additions and 48 deletions

View File

@ -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 {

View File

@ -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]

View File

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

View File

@ -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![],
}
}
}

View File

@ -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);