feat: implement new data display component
This commit is contained in:
1 parent
07c1679e1a
commit
eae8d2a26a
6 files changed
+488
-337
No files matched your search
@@ -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
@@ -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,16 +95,28 @@ 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 {
|
||||||
@@ -86,13 +124,130 @@ impl SchemaNode {
|
|||||||
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+188
-200
@@ -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
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
.into_any()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</DisplayWrapper>
|
</Show>
|
||||||
|
}
|
||||||
|
.into_any()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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::*;
|
|
||||||
@@ -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" />
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user