From c850ac208ff9212a9063b1bc9cccbe843b8e015e Mon Sep 17 00:00:00 2001 From: Lucas Vas Date: Fri, 20 Feb 2026 08:56:13 -0500 Subject: [PATCH] feat: finish initial data display component --- src/components/data_display.rs | 242 +++++++++++++++----------- src/pages/student/eligibility_page.rs | 91 +++------- src/pages/student/test_display.rs | 40 +++-- 3 files changed, 202 insertions(+), 171 deletions(-) diff --git a/src/components/data_display.rs b/src/components/data_display.rs index 5c2a1d1..1534bf9 100644 --- a/src/components/data_display.rs +++ b/src/components/data_display.rs @@ -1,8 +1,8 @@ use crate::common::{SchemaNode, SchemaType, ValueType}; -use leptos::prelude::*; -use std::collections::HashMap; use indexmap::IndexMap; use leptos::logging::debug_log; +use leptos::prelude::*; +use std::collections::HashMap; #[component] pub fn ValueDisplay( @@ -14,7 +14,8 @@ pub fn ValueDisplay( if data_member.get().is_empty() { ValueType::Map(Some(data_map.get())) } else { - data_map.get() + data_map + .get() .get(&data_member.get()) .map(|v| v.clone()) .unwrap_or_default() @@ -24,55 +25,101 @@ pub fn ValueDisplay( view! { {move || { match schema.get().data_type { - SchemaType::String | SchemaType::Number => view! { - - }.into_any(), - SchemaType::List => view! { - - }.into_any(), - SchemaType::Map => view! { - - }.into_any() + SchemaType::String | SchemaType::Number => { + view! { + + } + .into_any() + } + SchemaType::List => { + view! { + + } + .into_any() + } + SchemaType::Map => { + view! { + + } + .into_any() + } } }} } } +#[component] +fn DisplayWrapper( + #[prop(into, optional)] header: Signal, + #[prop(into, optional)] nested: Signal, + children: Children, +) -> impl IntoView { + // This component is the wrapper that is used in the rest of the display components. + // It allows for simpler styling of the header and capsule, along with simple collapsable + // functionality. + + let expanded = RwSignal::new(true); + + view! { +
+
+ {header} +
+
+
+
{children()}
+
+
+
+ } +} + #[component] fn PrimitiveDisplay( #[prop(into)] header: Signal, - #[prop(into)] value: Signal + #[prop(into)] value: Signal, + #[prop(into, optional)] nested: Signal, ) -> impl IntoView { let display_string = Memo::new(move |_| { let value = value.get(); match value { - ValueType::String(_) | ValueType::Number(_) => { - value.to_string() - } - _ => { - "Value is not a string.".to_string() - } + ValueType::String(_) | ValueType::Number(_) => value.to_string(), + _ => "Value is not a string.".to_string(), } }); view! { -
- -
{header}
-
+
{display_string}
-
+ } } @@ -81,6 +128,7 @@ fn ListDisplay( #[prop(into)] item_template: Signal>, #[prop(into)] value: Signal, #[prop(into)] header: Signal, + #[prop(into, optional)] nested: Signal, ) -> impl IntoView { // Unwrap the value and check if it's the correct type. To be displayed with this component, it // MUST be a ValueType::List. @@ -89,45 +137,46 @@ fn ListDisplay( return Vec::new(); }; - list.into_iter().enumerate().collect::>() + list.into_iter() + .enumerate() + .collect::>() }); - let is_valid = Memo::new(move |_| { - matches!(value.get(), ValueType::List(_)) - }); + let is_valid = Memo::new(move |_| matches!(value.get(), ValueType::List(_))); - let item_header = Memo::new(move |_| { - item_template.get().header - }); + let item_header = Memo::new(move |_| item_template.get().header); view! { -
-
{header}
+ view! { - - }.into_any(), - SchemaType::List => view! { -
"Nested lists are not currently supported for display."
- }.into_any(), - SchemaType::Map => view! { - - }.into_any() + SchemaType::String | SchemaType::Number => { + view! { }.into_any() + } + SchemaType::List => { + view! { +
"Nested lists are not currently supported for display."
+ } + .into_any() + } + SchemaType::Map => { + let map_header = format!("{} {}", item_template.get().header, idx + 1); + view! { + + } + .into_any() + } } } /> -
+ } } @@ -136,6 +185,7 @@ fn MapDisplay( #[prop(into)] value: Signal, #[prop(into)] header: Signal, #[prop(into)] schema: Signal>, + #[prop(into, optional)] nested: Signal, ) -> impl IntoView { // Unwrap the value and check if it is the correct type. It must be a ValueType::Map. let display_map = Memo::new(move |_| { @@ -150,55 +200,51 @@ fn MapDisplay( debug_log!("Current map values: {:?}", display_map.get()); }); - let is_valid = Memo::new(move |_| { - matches!(value.get(), ValueType::Map(_)) - }); + let is_valid = Memo::new(move |_| matches!(value.get(), ValueType::Map(_))); view! { -
-
{header}
-
- + { + view! { + + } + .into_any() + } + SchemaType::List => { + let template = child_schema + .item_template + .unwrap_or(Box::from(SchemaNode::new(SchemaType::List))); - match child_schema.data_type { - SchemaType::String | SchemaType::Number => view! { - - }.into_any(), - SchemaType::List => { - let template = child_schema.item_template - .unwrap_or(Box::from(SchemaNode::new(SchemaType::List))); - - view! { - - } - }.into_any(), - SchemaType::Map => view! { + } + .into_any() + } + SchemaType::Map => { + view! { - }.into_any() + } + .into_any() } } - /> -
-
+ } + /> + } } diff --git a/src/pages/student/eligibility_page.rs b/src/pages/student/eligibility_page.rs index 9f761dc..b880f29 100644 --- a/src/pages/student/eligibility_page.rs +++ b/src/pages/student/eligibility_page.rs @@ -1,9 +1,10 @@ +use crate::common::{ComparisonData, ExpandableInfo, SchemaNode, SchemaType, ValueType}; +use crate::components::ValueDisplay; +use crate::pages::api::students::get_all_student_data; +use crate::pages::api::{get_all_scholarship_info, get_comparison_info}; +use crate::utils::get_user_claims; use leptos::logging::debug_log; use leptos::prelude::*; -use crate::common::{ComparisonData, ExpandableInfo, ValueType}; -use crate::pages::api::{get_all_scholarship_info, get_comparison_info}; -use crate::pages::api::students::get_all_student_data; -use crate::utils::get_user_claims; #[component] pub fn StudentEligibilityPage() -> impl IntoView { @@ -13,37 +14,28 @@ pub fn StudentEligibilityPage() -> impl IntoView { // Then, for each scholarship found, we'll check every relation and filter out scholarships that // do not pass the check. We'll show some sort of simple list to indicate what scholarships passed // the check, and another to indicate which ones didn't. - + // My only reservation with showing the scholarships that aren't valid is that people will want // to know why. That's hard information to keep track of right now. - + let user_claims = get_user_claims(); - let user_id = Memo::new(move |_| { - user_claims.get() - .map(|info| info.claims.subject.clone()) - }); - + let user_id = Memo::new(move |_| user_claims.get().map(|info| info.claims.subject.clone())); + let refresh_trigger = Trigger::new(); - + let scholarships_resource = Resource::new( move || refresh_trigger.track(), - async move |_| { - get_all_scholarship_info().await - } + async move |_| get_all_scholarship_info().await, ); let relations_resource = Resource::new( move || refresh_trigger.track(), - async move |_| { - get_comparison_info().await - } + async move |_| get_comparison_info().await, ); let student_resource = Resource::new( move || (user_id.get().unwrap_or_default(), refresh_trigger.track()), - async move |(user_id, _)| { - get_all_student_data(user_id).await - } + async move |(user_id, _)| get_all_student_data(user_id).await, ); - + view! {
@@ -131,15 +123,6 @@ pub fn StudentEligibilityPage() -> impl IntoView { } }) .collect::>(); - - // Each scholarship will return Some(itself), or None. This is will be automatically - // filtered based on the information. Invalid scholarships are None, along with those - // that fail validation for any reason. Scholarships that are valid or did not choose - // any requirements are Some(scholarship). - - // We want to cast to a list, and then map the list to a new - // one that contains the correct ComparisonData. - view! {
{format!("Valid scholarships: {}", valid_list.len())}
impl IntoView { key=|scholarship| { scholarship.subject.clone() } children=move |scholarship| { let scholarship = StoredValue::new(scholarship); - let scholarship_name = Memo::new(move |_| { - scholarship - .read_value() - .data - .get("name") - .unwrap_or(&ValueType::String(None)) - .as_string() - .ok() - .flatten() - .unwrap_or_default() - }); - let scholarship_essay_prompt = Memo::new(move |_| { - scholarship - .read_value() - .data - .get("essay_prompt") - .unwrap_or(&ValueType::String(None)) - .as_string() - .ok() - .flatten() - .unwrap_or_default() - }); - let essay_required = Memo::new(move |_| { - !scholarship_essay_prompt.get().is_empty() - }); + let display_schema = SchemaNode::new(SchemaType::Map) + .header("Eligible Scholarship") + .child( + "name", + SchemaNode::new(SchemaType::String) + .header("Scholarship Name"), + ) + .child( + "essay_prompt", + SchemaNode::new(SchemaType::String).header("Essay Prompt"), + ); view! { -
-
{scholarship_name}
- -
{scholarship_essay_prompt}
-
-
+ } } /> diff --git a/src/pages/student/test_display.rs b/src/pages/student/test_display.rs index a0fa461..a66d8eb 100644 --- a/src/pages/student/test_display.rs +++ b/src/pages/student/test_display.rs @@ -1,22 +1,38 @@ +use crate::common::{SchemaNode, SchemaType}; use crate::components::{Loading, ValueDisplay}; use crate::pages::api::students::get_all_student_data; use crate::utils::get_user_claims; use leptos::prelude::*; -use crate::common::{SchemaNode, SchemaType}; #[component] pub fn StudentTestDisplay() -> impl IntoView { let create_student_display_schema = move || { SchemaNode::new(SchemaType::Map) - .header("Student Information:") - .child("first_name", SchemaNode::new(SchemaType::String).header("First Name:")) - .child("last_name", SchemaNode::new(SchemaType::String).header("Last Name:")) - .child("sports_participation", SchemaNode::new(SchemaType::List) - .header("List of Sports Activities") - .item_template(SchemaNode::new(SchemaType::Map) - .child("sport_name", SchemaNode::new(SchemaType::String).header("Sport Name:")) - .child("achievements", SchemaNode::new(SchemaType::String).header("Special Achievements")) - ) + .header("Student Information") + .child( + "first_name", + SchemaNode::new(SchemaType::String).header("First Name"), + ) + .child( + "last_name", + SchemaNode::new(SchemaType::String).header("Last Name"), + ) + .child( + "sports_participation", + SchemaNode::new(SchemaType::List) + .header("Sports Activities") + .item_template( + SchemaNode::new(SchemaType::Map) + .header("Sport") + .child( + "sport_name", + SchemaNode::new(SchemaType::String).header("Sport Name"), + ) + .child( + "achievements", + SchemaNode::new(SchemaType::String).header("Special Achievements"), + ), + ), ) }; @@ -31,8 +47,9 @@ pub fn StudentTestDisplay() -> impl IntoView { let schema = StoredValue::new(create_student_display_schema()); view! { +
-
+
{move || { resource .get() @@ -55,5 +72,6 @@ pub fn StudentTestDisplay() -> impl IntoView { }}
+
} }