feat: finish initial data display component
This commit is contained in:
1 parent
0816f2da8b
commit
c850ac208f
3 files changed
+165
-134
No files matched your search
+113
-67
@@ -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! {
|
||||
SchemaType::String | SchemaType::Number => {
|
||||
view! {
|
||||
<PrimitiveDisplay
|
||||
header={move || schema.get().header}
|
||||
header=move || schema.get().header
|
||||
value=data_value
|
||||
nested=true
|
||||
/>
|
||||
}.into_any(),
|
||||
SchemaType::List => view! {
|
||||
}
|
||||
.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)))
|
||||
item_template=move || {
|
||||
schema
|
||||
.get()
|
||||
.item_template
|
||||
.unwrap_or(Box::from(SchemaNode::new(SchemaType::List)))
|
||||
}
|
||||
nested=true
|
||||
/>
|
||||
}.into_any(),
|
||||
SchemaType::Map => view! {
|
||||
}
|
||||
.into_any()
|
||||
}
|
||||
SchemaType::Map => {
|
||||
view! {
|
||||
<MapDisplay
|
||||
value=data_value
|
||||
header=move || schema.get().header
|
||||
schema=move || schema.get().children
|
||||
nested=true
|
||||
/>
|
||||
}.into_any()
|
||||
}
|
||||
.into_any()
|
||||
}
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn DisplayWrapper(
|
||||
#[prop(into, optional)] header: Signal<String>,
|
||||
#[prop(into, optional)] nested: Signal<bool>,
|
||||
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! {
|
||||
<div class="m-3 rounded-lg shadow-lg flex flex-col">
|
||||
<div
|
||||
class="p-2 px-3 font-bold rounded-t-lg cursor-pointer"
|
||||
class=(["bg-gray-300"], move || !nested.get())
|
||||
class=(["text-white", "bg-red-900"], move || nested.get())
|
||||
class=(["rounded-lg"], move || !expanded.get())
|
||||
on:click=move |_| expanded.update(|b| *b = !*b)
|
||||
>
|
||||
{header}
|
||||
</div>
|
||||
<div
|
||||
class="grid transition-all ease-in-out"
|
||||
style:grid-template-rows=move || if expanded.get() { "1fr" } else { "0fr" }
|
||||
>
|
||||
<div class="min-h-0 overflow-hidden">
|
||||
<div class="p-2">{children()}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn PrimitiveDisplay(
|
||||
#[prop(into)] header: Signal<String>,
|
||||
#[prop(into)] value: Signal<ValueType>
|
||||
#[prop(into)] value: Signal<ValueType>,
|
||||
#[prop(into, optional)] nested: Signal<bool>,
|
||||
) -> 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! {
|
||||
<div>
|
||||
<Show when=move || !header.get().is_empty()>
|
||||
<div class="font-bold">{header}</div>
|
||||
</Show>
|
||||
<DisplayWrapper header=header nested=nested>
|
||||
<div>{display_string}</div>
|
||||
</div>
|
||||
</DisplayWrapper>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +128,7 @@ fn ListDisplay(
|
||||
#[prop(into)] item_template: Signal<Box<SchemaNode>>,
|
||||
#[prop(into)] value: Signal<ValueType>,
|
||||
#[prop(into)] header: Signal<String>,
|
||||
#[prop(into, optional)] nested: Signal<bool>,
|
||||
) -> 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::<Vec<(usize, ValueType)>>()
|
||||
list.into_iter()
|
||||
.enumerate()
|
||||
.collect::<Vec<(usize, ValueType)>>()
|
||||
});
|
||||
|
||||
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! {
|
||||
<div>
|
||||
<div class="font-bold">{header}</div>
|
||||
<DisplayWrapper header=header nested=nested>
|
||||
<For
|
||||
each=move || display_list.get()
|
||||
key=|(idx, _)| idx.clone()
|
||||
children=move |(idx, entry)| {
|
||||
match item_template.get().data_type {
|
||||
SchemaType::String | SchemaType::Number =>view! {
|
||||
<PrimitiveDisplay
|
||||
value=entry
|
||||
header=item_header
|
||||
/>
|
||||
}.into_any(),
|
||||
SchemaType::List => view! {
|
||||
SchemaType::String | SchemaType::Number => {
|
||||
view! { <PrimitiveDisplay value=entry header=item_header /> }.into_any()
|
||||
}
|
||||
SchemaType::List => {
|
||||
view! {
|
||||
<div>"Nested lists are not currently supported for display."</div>
|
||||
}.into_any(),
|
||||
SchemaType::Map => view! {
|
||||
}
|
||||
.into_any()
|
||||
}
|
||||
SchemaType::Map => {
|
||||
let map_header = format!("{} {}", item_template.get().header, idx + 1);
|
||||
view! {
|
||||
<MapDisplay
|
||||
value=entry
|
||||
header=item_header
|
||||
header=map_header
|
||||
schema=move || item_template.get().children
|
||||
/>
|
||||
}.into_any()
|
||||
}
|
||||
.into_any()
|
||||
}
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</DisplayWrapper>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,6 +185,7 @@ 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 |_| {
|
||||
@@ -150,34 +200,27 @@ 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! {
|
||||
<div class="flex flex-col flex-1 gap-2">
|
||||
<div class="font-bold">{header}</div>
|
||||
<div class="p-2 rounded-md shadow-lg/33 m-2">
|
||||
<DisplayWrapper header=header nested=nested>
|
||||
<For
|
||||
each=move || schema.get()
|
||||
key=|(k, _)| k.clone()
|
||||
children=move |(member, child_schema)| {
|
||||
let display_value = Memo::new(move |_| {
|
||||
display_map.get()
|
||||
.get(&member)
|
||||
.map(|v| v.clone())
|
||||
.unwrap_or_default()
|
||||
display_map.get().get(&member).map(|v| v.clone()).unwrap_or_default()
|
||||
});
|
||||
|
||||
match child_schema.data_type {
|
||||
SchemaType::String | SchemaType::Number => view! {
|
||||
<PrimitiveDisplay
|
||||
value=display_value
|
||||
header=child_schema.header
|
||||
/>
|
||||
}.into_any(),
|
||||
SchemaType::String | SchemaType::Number => {
|
||||
view! {
|
||||
<PrimitiveDisplay value=display_value header=child_schema.header />
|
||||
}
|
||||
.into_any()
|
||||
}
|
||||
SchemaType::List => {
|
||||
let template = child_schema.item_template
|
||||
let template = child_schema
|
||||
.item_template
|
||||
.unwrap_or(Box::from(SchemaNode::new(SchemaType::List)));
|
||||
|
||||
view! {
|
||||
@@ -187,18 +230,21 @@ fn MapDisplay(
|
||||
header=child_schema.header
|
||||
/>
|
||||
}
|
||||
}.into_any(),
|
||||
SchemaType::Map => view! {
|
||||
.into_any()
|
||||
}
|
||||
SchemaType::Map => {
|
||||
view! {
|
||||
<MapDisplay
|
||||
value=display_value
|
||||
schema=child_schema.children
|
||||
header=child_schema.header
|
||||
/>
|
||||
}.into_any()
|
||||
}
|
||||
.into_any()
|
||||
}
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</DisplayWrapper>
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
@@ -18,30 +19,21 @@ pub fn StudentEligibilityPage() -> impl IntoView {
|
||||
// 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::<Vec<ExpandableInfo>>();
|
||||
|
||||
// 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! {
|
||||
<div>{format!("Valid scholarships: {}", valid_list.len())}</div>
|
||||
<For
|
||||
@@ -147,39 +130,23 @@ pub fn StudentEligibilityPage() -> 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! {
|
||||
<div class="flex flex-1 flex-row gap-2 p-3">
|
||||
<div>{scholarship_name}</div>
|
||||
<Show when=move || essay_required.get()>
|
||||
<div>{scholarship_essay_prompt}</div>
|
||||
</Show>
|
||||
</div>
|
||||
<ValueDisplay
|
||||
schema=display_schema
|
||||
data_map=scholarship.get_value().data
|
||||
/>
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -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! {
|
||||
<div class="flex flex-1" />
|
||||
<Suspense fallback=Loading>
|
||||
<div class="flex flex-1 flex-col">
|
||||
<div class="flex flex-2 flex-col">
|
||||
{move || {
|
||||
resource
|
||||
.get()
|
||||
@@ -55,5 +72,6 @@ pub fn StudentTestDisplay() -> impl IntoView {
|
||||
}}
|
||||
</div>
|
||||
</Suspense>
|
||||
<div class="flex flex-1" />
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user