feat: implement new data display component

This commit is contained in:
alphafetus committed 2026-04-13 00:20:49 -04:00
1 parent 07c1679e1a
commit eae8d2a26a
6 files changed
+485 -334

No files matched your search

-1
View File
@@ -158,7 +158,6 @@ fn StudentRoutes() -> impl MatchNestedRoutes + Clone {
path=path!("additional/specifics") path=path!("additional/specifics")
view=student::StudentSpecificRequirementsPage view=student::StudentSpecificRequirementsPage
/> />
<Route path=path!("testing") view=student::StudentTestDisplay />
</ParentRoute> </ParentRoute>
} }
.into_inner() .into_inner()
+169 -14
View File
@@ -1,13 +1,35 @@
use std::collections::HashMap; use indexmap::IndexMap;
use indexmap::IndexMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Default, Debug, Clone)]
pub enum SchemaType { pub enum SchemaType {
/// Indicates a `ValueType::String` value.
#[default]
String, String,
/// Indicates a `ValueType::Number` value.
Number, Number,
List, /// Indicates a `ValueType::List` containing only strings or numbers.
Map PrimitiveList,
/// Indicates a `ValueType::List` containing only maps.
MapList,
/// Indicates a `ValueType::Map`.
Map,
}
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
pub enum SchemaContainerStyle {
#[default]
Header,
Capsule,
}
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
pub enum SchemaHeaderStyle {
#[default]
None,
MainHeader,
SubsectionHeader,
Bold,
} }
/// # Schema Node /// # Schema Node
@@ -55,12 +77,16 @@ pub enum SchemaType {
/// This schema does not currently support display of a single nested member, like a single map /// This schema does not currently support display of a single nested member, like a single map
/// within a list. This means that you will have to display any listed or mapped information as a /// within a list. This means that you will have to display any listed or mapped information as a
/// list. Consider this when designing the data. /// list. Consider this when designing the data.
#[derive(Deserialize, Serialize, Debug, Clone)] #[derive(Serialize, Deserialize, Default, Debug, Clone)]
pub struct SchemaNode { pub struct SchemaNode {
/// The display style of the data. Refer to [`SchemaContainerStyle`] for more information.
pub container_style: SchemaContainerStyle,
/// The datatype of the data found using the `data_member` key. /// The datatype of the data found using the `data_member` key.
pub data_type: SchemaType, pub data_type: SchemaType,
/// The header of this display. /// The header of this display.
pub header: String, pub header: String,
/// The style of the header's display.
pub header_style: SchemaHeaderStyle,
/// When used with a SchemaType::List, this determines the datatype and children for every entry /// When used with a SchemaType::List, this determines the datatype and children for every entry
/// in the list. Some examples include a list of maps, where we use the `item_template` to define /// in the list. Some examples include a list of maps, where we use the `item_template` to define
/// the SchemaType::Map, and all its children. This is then mapped across all entries in the list, /// the SchemaType::Map, and all its children. This is then mapped across all entries in the list,
@@ -69,30 +95,159 @@ pub struct SchemaNode {
/// When used with a SchemaType::Map, this determines what children from the map are displayed /// When used with a SchemaType::Map, this determines what children from the map are displayed
/// and how. It is ignored with all other SchemaType variants. /// and how. It is ignored with all other SchemaType variants.
pub children: IndexMap<String, SchemaNode>, pub children: IndexMap<String, SchemaNode>,
/// The primary key for iteration over maps. Used when there is a list of maps.
pub primary_key: Option<String>,
} }
impl SchemaNode { impl SchemaNode {
pub fn new(data_type: SchemaType) -> Self { /// Creates a new [`SchemaBuilder`].
SchemaNode { pub fn builder() -> SchemaBuilder {
data_type, SchemaBuilder::default()
header: "".to_string(),
item_template: None,
children: IndexMap::new(),
} }
} }
#[derive(Default, Debug, Clone)]
pub struct SchemaBuilder {
container_style: SchemaContainerStyle,
header: String,
header_style: SchemaHeaderStyle,
}
impl SchemaBuilder {
pub fn container(mut self, style: SchemaContainerStyle) -> Self {
self.container_style = style;
self
}
pub fn header(mut self, header: impl Into<String>) -> Self { pub fn header(mut self, header: impl Into<String>) -> Self {
self.header = header.into(); self.header = header.into();
self self
} }
pub fn child(mut self, data_member: impl Into<String>, node: SchemaNode) -> Self { pub fn header_style(mut self, header_style: SchemaHeaderStyle) -> Self {
self.children.insert(data_member.into(), node); self.header_style = header_style;
self self
} }
pub fn primitive_list(self) -> SchemaPrimitiveListBuilder {
SchemaPrimitiveListBuilder::new(self)
}
pub fn map_list(self) -> SchemaMapListBuilder {
SchemaMapListBuilder::new(self)
}
pub fn map(self) -> SchemaMapBuilder {
SchemaMapBuilder::new(self)
}
pub fn string(self) -> SchemaPrimitiveBuilder {
SchemaPrimitiveBuilder::new(self, SchemaType::String)
}
pub fn number(self) -> SchemaPrimitiveBuilder {
SchemaPrimitiveBuilder::new(self, SchemaType::Number)
}
fn into_node(self) -> SchemaNode {
SchemaNode {
container_style: self.container_style,
header: self.header,
header_style: self.header_style,
..Default::default()
}
}
}
#[derive(Default, Debug, Clone)]
pub struct SchemaPrimitiveListBuilder {
builder: SchemaBuilder,
}
impl SchemaPrimitiveListBuilder {
fn new(builder: SchemaBuilder) -> Self {
Self { builder }
}
}
#[derive(Default, Debug, Clone)]
pub struct SchemaMapListBuilder {
builder: SchemaBuilder,
item_template: Option<Box<SchemaNode>>,
primary_key: String,
}
impl SchemaMapListBuilder {
fn new(builder: SchemaBuilder) -> Self {
Self {
builder,
..Default::default()
}
}
pub fn item_template(mut self, node: SchemaNode) -> Self { pub fn item_template(mut self, node: SchemaNode) -> Self {
self.item_template = Some(Box::new(node)); self.item_template = Some(Box::new(node));
self self
} }
pub fn primary_key(mut self, primary_key: impl Into<String>) -> Self {
self.primary_key = primary_key.into();
self
}
pub fn build(self) -> SchemaNode {
SchemaNode {
data_type: SchemaType::MapList,
item_template: self.item_template,
primary_key: Some(self.primary_key),
..self.builder.into_node()
}
}
}
#[derive(Default, Debug, Clone)]
pub struct SchemaMapBuilder {
builder: SchemaBuilder,
children: IndexMap<String, SchemaNode>,
}
impl SchemaMapBuilder {
fn new(builder: SchemaBuilder) -> Self {
Self {
builder,
..Default::default()
}
}
pub fn child(mut self, data_member: impl Into<String>, node: SchemaNode) -> Self {
self.children.insert(data_member.into(), node);
self
}
pub fn build(self) -> SchemaNode {
SchemaNode {
data_type: SchemaType::Map,
children: self.children,
..self.builder.into_node()
}
}
}
#[derive(Default, Debug, Clone)]
pub struct SchemaPrimitiveBuilder {
builder: SchemaBuilder,
data_type: SchemaType,
}
impl SchemaPrimitiveBuilder {
fn new(builder: SchemaBuilder, data_type: SchemaType) -> Self {
Self { data_type, builder }
}
pub fn build(self) -> SchemaNode {
SchemaNode {
data_type: self.data_type,
..self.builder.into_node()
}
}
} }
+185 -197
View File
@@ -1,265 +1,253 @@
use crate::common::{SchemaNode, SchemaType, ValueType}; use crate::common::{SchemaContainerStyle, SchemaHeaderStyle, SchemaNode, SchemaType, ValueType};
use indexmap::IndexMap;
use leptos::logging::debug_log;
use leptos::prelude::*; use leptos::prelude::*;
use std::collections::HashMap; use std::collections::HashMap;
/// # Value Display Component
/// This component displays any data from a given `HashMap<String, ValueType>`. It requires a schema
/// to tell it how to display the information, which is covered in the [`SchemaNode`] documentation.
#[component] #[component]
pub fn ValueDisplay( pub fn DataDisplay(
#[prop(into)] schema: Signal<SchemaNode>, /// The schema that this component should use to display the given data.
/// The starting member. Starts display from this member in the `data_map`, if it exists. #[prop(into)]
schema: Signal<SchemaNode>,
/// The starting key in the data map.
#[prop(into, optional)] #[prop(into, optional)]
data_member: Signal<String>, data_member: Signal<String>,
#[prop(into)] data_map: Signal<HashMap<String, ValueType>>, /// A `HashMap` containing all the data that should be displayed.
#[prop(into)]
data_map: Signal<HashMap<String, ValueType>>,
) -> impl IntoView { ) -> impl IntoView {
let data_value = Memo::new(move |_| { // The schema should contain information about the header type and styling, along with the
if data_member.get().is_empty() { // information display. This is rendered as part of the DataDisplayWrapper, which is simply a
ValueType::Map(Some(data_map.get())) // wrapper around the information to be displayed.
// The children are all SchemaNodes as well, meaning that this component needs to call itself
// for each of these children. They will of course contain all the same information that we
// expect here.
// The actual displayed information is controlled through a DataDisplayView, which will accept
// settings for how the information is parsed. For example, rendering a list may be done by
// a string join operation, or it may be a list of maps, each of which needs to be displayed with
// a specific set of children. The SchemaNode must be able to handle this, and these options
// should be given to the DataDisplayView somehow.
// As the display_value here is actually an Option<ValueType>, we want to be able to handle cases
// where the display is given no values. In this case, the SchemaNode needs to specify whether
// a missing value is expected or an error.
let display_value = Memo::new(move |_| {
let member = data_member.get();
if member.is_empty() {
Some(ValueType::Map(Some(data_map.get())))
} else { } else {
data_map data_map.get().get(&member).cloned()
.get()
.get(&data_member.get())
.map(|v| v.clone())
.unwrap_or_default()
} }
}); });
view! { view! {
{move || { <DataDisplayWrapper
match schema.get().data_type { container_style=schema.get().container_style
SchemaType::String | SchemaType::Number => { header_style=schema.get().header_style
view! { header_text=schema.get().header
<PrimitiveDisplay >
header=move || schema.get().header <DataDisplayView display_data=display_value schema=schema />
value=data_value </DataDisplayWrapper>
nested=true
/>
}
.into_any()
}
SchemaType::List => {
view! {
<ListDisplay
header=move || schema.get().header
value=data_value
item_template=move || {
schema
.get()
.item_template
.unwrap_or(Box::from(SchemaNode::new(SchemaType::List)))
}
nested=true
/>
}
.into_any()
}
SchemaType::Map => {
view! {
<MapDisplay
value=data_value
header=move || schema.get().header
schema=move || schema.get().children
nested=true
/>
}
.into_any()
}
}
}}
} }
} }
/// # Display Wrapper Component
/// This component is designed to wrap information for the [`ValueDisplay`] component. It allows
/// for simple uniform styling of the header and capsule, along with the ability for users to
/// collapse the information.
#[component] #[component]
fn DisplayWrapper( fn DataDisplayWrapper(
#[prop(into, optional)] header: Signal<String>, #[prop(into)] container_style: SchemaContainerStyle,
#[prop(into, optional)] nested: Signal<bool>, #[prop(into)] header_style: SchemaHeaderStyle,
#[prop(into)] header_text: Signal<String>,
children: Children, children: Children,
) -> impl IntoView { ) -> impl IntoView {
// This component takes a wrapper style and displays the "chrome" of the interface. It has two
// options: Text Headers or Capsules. This will then render a DataDisplayHeader, which handles
// the styling for the text inside the header specifically.
// The capsule style is a colored header and a rounded container.
// The text header style is an invisible container.
let expanded = RwSignal::new(true); let expanded = RwSignal::new(true);
match container_style {
SchemaContainerStyle::Header => {
// Creates a simple container that only has a header and a series of children.
// The spacing between the header and children is controlled by the header style.
// This type of container is not collapsable at the moment.
view! { view! {
<div class="m-3 rounded-lg shadow-lg flex flex-col"> <div class="flex flex-col w-full text-left">
<div class="self-start w-full">
<DataDisplayHeader header_text=header_text header_style=header_style />
<div class="p-2 flex flex-col gap-1">{children()}</div>
</div>
</div>
}
.into_any()
}
SchemaContainerStyle::Capsule => {
// Creates a capsule container - it is collapsable, with rounded corners and a colored
// header.
view! {
<div class="self-stretch rounded-lg shadow-lg flex flex-col w-full text-center">
<div <div
class="p-2 px-3 font-bold rounded-t-lg cursor-pointer" class="p-2 px-3 rounded-t-lg cursor-pointer bg-red-800 text-white"
class=(["bg-gray-300"], move || !nested.get())
class=(["text-white", "bg-red-900"], move || nested.get())
class=(["rounded-lg"], move || !expanded.get()) class=(["rounded-lg"], move || !expanded.get())
on:click=move |_| expanded.update(|b| *b = !*b) on:click=move |_| expanded.update(|b| *b = !*b)
> >
{header} <DataDisplayHeader header_text=header_text header_style=header_style />
</div> </div>
<div <div
class="grid transition-all ease-in-out" class="grid transition-all ease-in-out"
style:grid-template-rows=move || if expanded.get() { "1fr" } else { "0fr" } style:grid-template-rows=move || if expanded.get() { "1fr" } else { "0fr" }
> >
<div class="min-h-0 overflow-hidden"> <div class="min-h-0 overflow-hidden">
<div class="p-2">{children()}</div> <div class="p-2 flex flex-col gap-1 flex-1">{children()}</div>
</div> </div>
</div> </div>
</div> </div>
} }
.into_any()
}
}
} }
/// # Primitive Value Display Component
/// This component allows for the display of a `ValueType::String` or `ValueType::Number`.
#[component] #[component]
fn PrimitiveDisplay( fn DataDisplayHeader(
#[prop(into)] header: Signal<String>, #[prop(into)] header_style: SchemaHeaderStyle,
#[prop(into)] value: Signal<ValueType>, #[prop(into)] header_text: Signal<String>,
#[prop(into, optional)] nested: Signal<bool>,
) -> impl IntoView { ) -> impl IntoView {
let display_string = Memo::new(move |_| { // This component will take a header style and displays the corresponding text. It has a few
let value = value.get(); // options, all of which are defined in the HeaderStyle enum.
match value {
ValueType::String(_) | ValueType::Number(_) => value.to_string(),
_ => "Value is not a string.".to_string(),
}
});
view! { match header_style {
<DisplayWrapper header=header nested=nested> SchemaHeaderStyle::MainHeader => {
<div>{display_string}</div> view! { <div class="text-3xl font-bold my-2">{header_text}</div> }.into_any()
</DisplayWrapper> }
SchemaHeaderStyle::SubsectionHeader => {
view! { <div class="text-xl font-bold">{header_text}</div> }.into_any()
}
SchemaHeaderStyle::Bold => view! { <div class="font-bold">{header_text}</div> }.into_any(),
SchemaHeaderStyle::None => view! {}.into_any(),
} }
} }
/// # List Value Display Component
/// This component allows for the display of a `ValueType::List`. It will show an error if passed
/// anything other than a list. It requires an item template that tells the list how to display
/// each value inside of it, most specifically the type of data inside of it.
#[component] #[component]
fn ListDisplay( fn DataDisplayView(
#[prop(into)] item_template: Signal<Box<SchemaNode>>, #[prop(into)] display_data: Signal<Option<ValueType>>,
#[prop(into)] value: Signal<ValueType>, #[prop(into)] schema: Signal<SchemaNode>,
#[prop(into)] header: Signal<String>,
#[prop(into, optional)] nested: Signal<bool>,
) -> impl IntoView { ) -> impl IntoView {
// Unwrap the value and check if it's the correct type. To be displayed with this component, it // This is the part of the data display that does most of the work. It will take the values
// MUST be a ValueType::List. // found in the DataDisplay component and perform the logic that actually displays the
let display_list = Memo::new(move |_| { // information. This component is only displayed if there is Some(value) in the DataDisplay.
let ValueType::List(Some(list)) = value.get() else {
return Vec::new();
};
list.into_iter() // In the case that this is displaying a Map or List, it will recursively call the DataDisplay
.enumerate() // component.
.collect::<Vec<(usize, ValueType)>>()
// If this is displaying a PrimitiveList, it will flatten and join all the values into a single
// string, then display that string.
let data_type = schema.get().data_type;
// Matches the specific data type. Displays errors if the given display data does not match.
match data_type {
SchemaType::String => {
let value = Signal::derive(move || {
display_data
.get()
.map(|v| v.as_string().ok().flatten())
.flatten()
.unwrap_or("N/A".to_string())
}); });
view! { <div>{value}</div> }.into_any()
let is_valid = Memo::new(move |_| matches!(value.get(), ValueType::List(_))); }
SchemaType::Number => {
let item_header = Memo::new(move |_| item_template.get().header); let value = Signal::derive(move || {
display_data
.get()
.map(|v| v.as_number().ok().flatten())
.flatten()
.unwrap_or("N/A".to_string())
});
view! { <div>{value}</div> }.into_any()
}
SchemaType::PrimitiveList => {
let value = Signal::derive(move || {
display_data
.get()
.map(|v| v.as_list().ok().flatten())
.flatten()
.unwrap_or_default()
.iter()
.map(|v| v.to_string())
.collect::<Vec<String>>()
.join(", ")
});
view! { <div>{value}</div> }.into_any()
}
SchemaType::MapList => {
let list = Signal::derive(move || {
display_data
.get()
.map(|v| v.as_list().ok().flatten())
.flatten()
.unwrap_or_default()
.iter()
.filter_map(|v| v.as_map().ok().flatten().clone())
.collect::<Vec<HashMap<String, ValueType>>>()
});
let primary_key = StoredValue::new(schema.get().primary_key.unwrap_or_default());
view! { view! {
<DisplayWrapper header=header nested=nested> <Show
when=move || !list.get().is_empty()
fallback=|| view! { <div class="w-full text-center">"List is empty."</div> }
>
<For <For
each=move || display_list.get() each=move || list.get()
key=|(idx, _)| idx.clone() key=move |map| {
children=move |(idx, entry)| { map.get(&primary_key.get_value())
match item_template.get().data_type { .map(|v| v.to_string())
SchemaType::String | SchemaType::Number => { .unwrap_or_default()
view! { <PrimitiveDisplay value=entry header=item_header /> }.into_any()
} }
SchemaType::List => { children=move |map| {
view! { view! {
<div>"Nested lists are not currently supported for display."</div> <DataDisplay
schema=*schema.get().item_template.unwrap_or_default()
data_map=map
/>
}
}
/>
</Show>
} }
.into_any() .into_any()
} }
SchemaType::Map => { SchemaType::Map => {
let map_header = format!("{} {}", item_template.get().header, idx + 1); let map = Signal::derive(move || {
view! { display_data
<MapDisplay .get()
value=entry .map(|v| v.as_map().ok().flatten())
header=map_header .flatten()
schema=move || item_template.get().children
/>
}
.into_any()
}
}
}
/>
</DisplayWrapper>
}
}
/// # Map Data Display Component
/// This component allows for the display of a `ValueType::Map`. The nested information must be defined
/// by a [`SchemaNode`], which tells the display what keys in the value map to search for the
/// display information.
#[component]
fn MapDisplay(
#[prop(into)] value: Signal<ValueType>,
#[prop(into)] header: Signal<String>,
#[prop(into)] schema: Signal<IndexMap<String, SchemaNode>>,
#[prop(into, optional)] nested: Signal<bool>,
) -> 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 |_| {
let ValueType::Map(Some(map)) = value.get() else {
return HashMap::new();
};
map
}); });
Effect::new(move || {
debug_log!("Current map values: {:?}", display_map.get());
});
let is_valid = Memo::new(move |_| matches!(value.get(), ValueType::Map(_)));
view! { view! {
<DisplayWrapper header=header nested=nested> <Show
when=move || map.get().is_some()
fallback=|| view! { <div class="text-red-500">"Map was not found."</div> }
>
<For <For
each=move || schema.get() each=move || schema.get().children
key=|(k, _)| k.clone() key=|(key, _)| key.clone()
children=move |(member, child_schema)| { children=move |(key, schema)| {
let display_value = Memo::new(move |_| {
display_map.get().get(&member).map(|v| v.clone()).unwrap_or_default()
});
match child_schema.data_type {
SchemaType::String | SchemaType::Number => {
view! { view! {
<PrimitiveDisplay value=display_value header=child_schema.header /> <DataDisplay
} schema=schema
.into_any() data_member=key
} data_map=Signal::derive(move || map.get().unwrap_or_default())
SchemaType::List => {
let template = child_schema
.item_template
.unwrap_or(Box::from(SchemaNode::new(SchemaType::List)));
view! {
<ListDisplay
value=display_value
item_template=template
header=child_schema.header
/> />
} }
.into_any()
} }
SchemaType::Map => {
view! {
<MapDisplay
value=display_value
schema=child_schema.children
header=child_schema.header
/> />
</Show>
} }
.into_any() .into_any()
} }
} }
} }
/>
</DisplayWrapper>
}
}
+129 -41
View File
@@ -1,5 +1,8 @@
use crate::common::{ComparisonData, ExpandableInfo, SchemaNode, SchemaType, ValueType}; use crate::common::{
use crate::components::{ActionButton, Banner, Loading, ValueDisplay}; ComparisonData, ExpandableInfo, SchemaContainerStyle, SchemaHeaderStyle, SchemaNode,
SchemaType, ValueType,
};
use crate::components::{ActionButton, Banner, DataDisplay, Loading};
use crate::pages::UnauthenticatedPage; use crate::pages::UnauthenticatedPage;
use crate::pages::api::get_comparison_info; use crate::pages::api::get_comparison_info;
use crate::pages::api::students::{GetStudentFiles, get_all_input_files, get_student_data}; use crate::pages::api::students::{GetStudentFiles, get_all_input_files, get_student_data};
@@ -9,7 +12,8 @@ use leptos::html::Dialog;
use leptos::logging::debug_log; use leptos::logging::debug_log;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::wasm_bindgen::JsCast; use leptos::wasm_bindgen::JsCast;
use leptos::web_sys::HtmlAnchorElement; use leptos::web_sys;
use leptos::web_sys::{HtmlAnchorElement, ScrollBehavior};
use leptos_icons::Icon; use leptos_icons::Icon;
use leptos_oidc::{AuthLoaded, AuthSignal, Authenticated}; use leptos_oidc::{AuthLoaded, AuthSignal, Authenticated};
use leptos_router::components::Outlet; use leptos_router::components::Outlet;
@@ -319,6 +323,14 @@ fn ApplicantsStudentListView(
// let (current_student_id, set_current_student_id) = query_signal::<String>("student_id"); // let (current_student_id, set_current_student_id) = query_signal::<String>("student_id");
let current_student_id = RwSignal::new(None); let current_student_id = RwSignal::new(None);
let navigate_student = Callback::new(move |id: String| { let navigate_student = Callback::new(move |id: String| {
let options = web_sys::ScrollToOptions::new();
options.set_top(0.0);
options.set_behavior(ScrollBehavior::Smooth);
if let Some(window) = web_sys::window() {
window.scroll_to_with_scroll_to_options(&options);
}
current_student_id.set(Some(id)); current_student_id.set(Some(id));
}); });
let dialog_visible = let dialog_visible =
@@ -591,93 +603,172 @@ fn StudentInformationDialog(
//#region Schema Definitions //#region Schema Definitions
let demographics_schema = StoredValue::new( let demographics_schema = StoredValue::new(
SchemaNode::new(SchemaType::Map) SchemaNode::builder()
.container(SchemaContainerStyle::Header)
.header_style(SchemaHeaderStyle::MainHeader)
.header("Demographics Information") .header("Demographics Information")
.map()
.child( .child(
"first_name", "first_name",
SchemaNode::new(SchemaType::String).header("First Name"), SchemaNode::builder()
.header_style(SchemaHeaderStyle::Bold)
.header("First Name")
.string()
.build(),
) )
.child( .child(
"last_name", "last_name",
SchemaNode::new(SchemaType::String).header("Last Name"), SchemaNode::builder()
.header_style(SchemaHeaderStyle::Bold)
.header("Last Name")
.string()
.build(),
) )
.child( .child(
"dob", "dob",
SchemaNode::new(SchemaType::String).header("Date of Birth"), SchemaNode::builder()
.header_style(SchemaHeaderStyle::Bold)
.header("Date of Birth")
.string()
.build(),
) )
.child( .child(
"gender", "gender",
SchemaNode::new(SchemaType::String).header("Gender"), SchemaNode::builder()
.header_style(SchemaHeaderStyle::Bold)
.header("Gender")
.string()
.build(),
) )
.child( .child(
"phone_number", "phone_number",
SchemaNode::new(SchemaType::String).header("Preferred Phone Number"), SchemaNode::builder()
.header_style(SchemaHeaderStyle::Bold)
.header("Preferred Phone Number")
.string()
.build(),
) )
.child( .child(
"email", "email",
SchemaNode::new(SchemaType::String).header("Preferred Email Address"), SchemaNode::builder()
.header_style(SchemaHeaderStyle::Bold)
.header("Preferred Email Address")
.string()
.build(),
) )
.child( .child(
"street_address", "street_address",
SchemaNode::new(SchemaType::String).header("Street Address"), SchemaNode::builder()
.header_style(SchemaHeaderStyle::Bold)
.header("Street Address")
.string()
.build(),
) )
.child("town", SchemaNode::new(SchemaType::String).header("Town")), .child(
"town",
SchemaNode::builder()
.header_style(SchemaHeaderStyle::Bold)
.header("Town")
.string()
.build(),
)
.build(),
); );
let sports_schema = StoredValue::new( let sports_schema = StoredValue::new(
SchemaNode::new(SchemaType::List) SchemaNode::builder()
.header_style(SchemaHeaderStyle::MainHeader)
.header("Sports Activities") .header("Sports Activities")
.map_list()
.primary_key("uuid")
.item_template( .item_template(
SchemaNode::new(SchemaType::Map) SchemaNode::builder()
.container(SchemaContainerStyle::Capsule)
.header_style(SchemaHeaderStyle::Bold)
.header("Sport") .header("Sport")
.map()
.child( .child(
"sport_name", "sport_name",
SchemaNode::new(SchemaType::String).header("Sport Name"), SchemaNode::builder()
.header_style(SchemaHeaderStyle::Bold)
.header("Sport Name")
.string()
.build(),
) )
.child( .child(
"achievements", "achievements",
SchemaNode::new(SchemaType::String).header("Special Achievements"), SchemaNode::builder()
), .header_style(SchemaHeaderStyle::Bold)
), .header("Special Achievements")
.string()
.build(),
)
.build(),
)
.build(),
); );
let extracurricular_schema = StoredValue::new( let extracurricular_schema = StoredValue::new(
SchemaNode::new(SchemaType::Map) SchemaNode::builder()
.header_style(SchemaHeaderStyle::MainHeader)
.header("Extracurricular Activities") .header("Extracurricular Activities")
.map()
.child( .child(
"service_hours", "service_hours",
SchemaNode::new(SchemaType::String).header("Total Number of Service Hours"), SchemaNode::builder()
.header_style(SchemaHeaderStyle::Bold)
.header("Total Number of Service Hours")
.number()
.build(),
) )
.child( .child(
"extracurricular", "extracurricular",
SchemaNode::new(SchemaType::List) SchemaNode::builder()
.header("List of Activities") .map_list()
.primary_key("uuid")
.item_template( .item_template(
SchemaNode::new(SchemaType::Map) SchemaNode::builder()
.container(SchemaContainerStyle::Capsule)
.header_style(SchemaHeaderStyle::Bold)
.header("Activity") .header("Activity")
.map()
.child( .child(
"activity_name", "activity_name",
SchemaNode::new(SchemaType::String).header("Activity Name"), SchemaNode::builder()
.header_style(SchemaHeaderStyle::Bold)
.header("Activity Name")
.string()
.build(),
) )
.child( .child(
"num_hours", "num_hours",
SchemaNode::new(SchemaType::String) SchemaNode::builder()
.header("Number of Hours Completed"), .header_style(SchemaHeaderStyle::Bold)
.header("Approximate Number of Hours Completed")
.number()
.build(),
) )
.child( .child(
"num_weeks", "num_weeks",
SchemaNode::new(SchemaType::String) SchemaNode::builder()
.header("Number of Weeks Participated"), .header_style(SchemaHeaderStyle::Bold)
.header("Approximate Number of Weeks Participated")
.number()
.build(),
) )
.child( .child(
"special_involvement", "special_involvement",
SchemaNode::new(SchemaType::String).header("Special Involvement"), SchemaNode::builder()
), // .child( .header_style(SchemaHeaderStyle::Bold)
// "grades", .header("Special Involvement")
// SchemaNode::new(SchemaType::String).header("Grades Participated") .string()
// ) .build(),
), )
), .build(),
)
.build(),
)
.build(),
); );
//#endregion //#endregion
@@ -758,9 +849,6 @@ fn StudentInformationDialog(
> >
<Suspense fallback=Loading> <Suspense fallback=Loading>
<h1 class="text-2xl font-bold">"Student Information"</h1> <h1 class="text-2xl font-bold">"Student Information"</h1>
<div class="text-lg">
"Click on any header to minimize that block of information."
</div>
<div class="flex flex-row m-1.5"> <div class="flex flex-row m-1.5">
<Show when=move || fafsa_required.get()> <Show when=move || fafsa_required.get()>
<ActionButton on:click=on_click_fafsa disabled=get_buttons_disabled> <ActionButton on:click=on_click_fafsa disabled=get_buttons_disabled>
@@ -781,7 +869,7 @@ fn StudentInformationDialog(
.await .await
.map(|student_info| { .map(|student_info| {
view! { view! {
<ValueDisplay <DataDisplay
schema=demographics_schema.get_value() schema=demographics_schema.get_value()
data_map=student_info data_map=student_info
/> />
@@ -796,7 +884,7 @@ fn StudentInformationDialog(
.await .await
.map(|student_info| { .map(|student_info| {
view! { view! {
<ValueDisplay <DataDisplay
schema=sports_schema.get_value() schema=sports_schema.get_value()
data_map=student_info data_map=student_info
data_member="sports_participation" data_member="sports_participation"
@@ -812,7 +900,7 @@ fn StudentInformationDialog(
.await .await
.map(|student_info| { .map(|student_info| {
view! { view! {
<ValueDisplay <DataDisplay
schema=extracurricular_schema.get_value() schema=extracurricular_schema.get_value()
data_map=student_info data_map=student_info
/> />
+2 -4
View File
@@ -9,10 +9,9 @@ mod financial_page;
mod form_setup; mod form_setup;
mod home_page; mod home_page;
mod shell; mod shell;
mod test_display; mod specific_requirements_page;
mod university_page; mod university_page;
mod work_experience_page; mod work_experience_page;
mod specific_requirements_page;
pub use academics_page::*; pub use academics_page::*;
pub use additional_page::*; pub use additional_page::*;
@@ -24,7 +23,6 @@ pub use family_info_page::*;
pub use financial_page::*; pub use financial_page::*;
pub use home_page::*; pub use home_page::*;
pub use shell::*; pub use shell::*;
pub use test_display::*; pub use specific_requirements_page::*;
pub use university_page::*; pub use university_page::*;
pub use work_experience_page::*; pub use work_experience_page::*;
pub use specific_requirements_page::*;
-77
View File
@@ -1,77 +0,0 @@
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::*;
#[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("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"),
),
),
)
};
let user_claims = get_user_claims();
let user_id = Memo::new(move |_| user_claims.get().map(|info| info.claims.subject.clone()));
let resource = Resource::new(
move || user_id.get().unwrap_or_default(),
async move |id| get_all_student_data(id).await,
);
let schema = StoredValue::new(create_student_display_schema());
view! {
<div class="flex flex-1" />
<Suspense fallback=Loading>
<div class="flex flex-2 flex-col">
{move || {
resource
.get()
.map(|data_map| {
let Ok(map) = data_map else {
return view! {}.into_any();
};
let data_map = StoredValue::new(map);
view! {
<div>
<ValueDisplay
schema=schema.get_value()
data_map=data_map.get_value()
/>
</div>
}
.into_any()
})
}}
</div>
</Suspense>
<div class="flex flex-1" />
}
}