Merge pull request #34 from R15-Software-Dev/feat/provider_portal

Provider Dashboard
This commit was merged in pull request #34.
This commit is contained in:
Morrighan authored and GitHub committed 2025-12-22 07:36:00 -05:00
commit c8be9a35cf
17 files changed
+348 -262

No files matched your search

+16 -10
View File
@@ -1,9 +1,12 @@
use leptos::logging::log; use crate::pages::{AboutPage, ComparisonTestPage, HomePage, LoanerPage, ProviderPortal, TestPage};
use leptos::leptos_dom::log;
use leptos::prelude::*; use leptos::prelude::*;
use leptos_meta::{MetaTags, Stylesheet, Title, provide_meta_context}; use leptos_meta::{MetaTags, Stylesheet, Title, provide_meta_context};
use leptos_oidc::{Auth, AuthParameters, AuthSignal, Challenge}; use leptos_oidc::{Auth, AuthParameters, AuthSignal, Challenge};
use leptos_router::{components::{Route, Router, Routes}, path}; use leptos_router::{
use crate::pages::{AboutPage, ComparisonTestPage, HomePage, LoanerPage, TestPage}; components::{Route, Router, Routes},
path,
};
pub fn shell(options: LeptosOptions) -> impl IntoView { pub fn shell(options: LeptosOptions) -> impl IntoView {
view! { view! {
@@ -32,7 +35,7 @@ pub fn App() -> impl IntoView {
// injects a stylesheet into the document <head> // injects a stylesheet into the document <head>
// id=leptos means cargo-leptos will hot-reload this stylesheet // id=leptos means cargo-leptos will hot-reload this stylesheet
<Stylesheet id="leptos" href="/pkg/scholarships-rs-wasm.css"/> <Stylesheet id="leptos" href="/pkg/scholarships-rs-wasm.css"/>
// sets the document title // sets the document title
<Title text="R15 Scholarship App DEV"/> <Title text="R15 Scholarship App DEV"/>
@@ -44,7 +47,8 @@ pub fn App() -> impl IntoView {
} }
fn use_origin() -> String { fn use_origin() -> String {
#[cfg(target_arch = "wasm32")] { #[cfg(target_arch = "wasm32")]
{
use leptos::web_sys; use leptos::web_sys;
// Just get the current URL origin. // Just get the current URL origin.
web_sys::window() web_sys::window()
@@ -53,8 +57,9 @@ fn use_origin() -> String {
.origin() .origin()
.unwrap_or("http://localhost:3000/".to_string()) .unwrap_or("http://localhost:3000/".to_string())
} }
#[cfg(not(target_arch = "wasm32"))] { #[cfg(not(target_arch = "wasm32"))]
{
// Read the expected origin out of an environment variable. // Read the expected origin out of an environment variable.
std::env::var("LP_SITE_ORIGIN").unwrap_or("http://localhost:3000/".to_string()) std::env::var("LP_SITE_ORIGIN").unwrap_or("http://localhost:3000/".to_string())
} }
@@ -63,7 +68,7 @@ fn use_origin() -> String {
#[component] #[component]
pub fn AppWithRoutes() -> impl IntoView { pub fn AppWithRoutes() -> impl IntoView {
provide_meta_context(); provide_meta_context();
let current_origin = use_origin(); let current_origin = use_origin();
log!("Current origin: {}", current_origin); log!("Current origin: {}", current_origin);
@@ -82,7 +87,7 @@ pub fn AppWithRoutes() -> impl IntoView {
let auth = Auth::signal(); let auth = Auth::signal();
provide_context(auth); // allows use of this signal in lower areas of the tree without provide_context(auth); // allows use of this signal in lower areas of the tree without
// explicitly passing it through the html tree // explicitly passing it through the html tree
let _ = Auth::init(parameters); let _ = Auth::init(parameters);
@@ -96,7 +101,7 @@ pub fn AppWithRoutes() -> impl IntoView {
// sets the document title // sets the document title
<Title text="R15 Scholarship App DEV" /> <Title text="R15 Scholarship App DEV" />
<main> <main>
// TODO Create a 404 page // TODO Create a 404 page
<Routes fallback=|| "Page not found.".into_view()> <Routes fallback=|| "Page not found.".into_view()>
@@ -104,6 +109,7 @@ pub fn AppWithRoutes() -> impl IntoView {
<Route path=path!("about") view=AboutPage/> <Route path=path!("about") view=AboutPage/>
<Route path=path!("test_page") view=TestPage/> <Route path=path!("test_page") view=TestPage/>
<Route path=path!("comparison") view=ComparisonTestPage /> <Route path=path!("comparison") view=ComparisonTestPage />
<Route path=path!("providers") view=ProviderPortal />
<Route path=path!("loaners") view=LoanerPage /> <Route path=path!("loaners") view=LoanerPage />
<Route path=path!("loaners/:form_name") view=LoanerPage /> <Route path=path!("loaners/:form_name") view=LoanerPage />
</Routes> </Routes>
+13 -8
View File
@@ -40,10 +40,15 @@ impl ComparisonData {
display_text: display_text.into(), display_text: display_text.into(),
} }
} }
pub fn compare(&self, student_data: &ExpandableInfo) -> Result<bool, String> { pub fn compare(&self, student_data: &ExpandableInfo) -> Result<bool, String> {
let value = student_data.data.get(&self.member) let value = student_data.data.get(&self.member).expect(
.expect(format!("Couldn't find member {:?}, current student data: {:?}", self.member, student_data).as_str()); format!(
"Couldn't find member {:?}, current student data: {:?}",
self.member, student_data
)
.as_str(),
);
self.comparison.evaluate(value, &self.target_value) self.comparison.evaluate(value, &self.target_value)
} }
} }
@@ -73,13 +78,13 @@ impl Comparison for ComparisonType {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::common::comparison::test_utils::create_student_data;
use super::*; use super::*;
use crate::common::comparison::test_utils::create_student_data;
#[test] #[test]
fn test_student_data() { fn test_student_data() {
let student_data = create_student_data(); let student_data = create_student_data();
// Should succeed every time. // Should succeed every time.
let text_comp_success = ComparisonData { let text_comp_success = ComparisonData {
id: "first_name_success".to_string(), id: "first_name_success".to_string(),
@@ -99,11 +104,11 @@ mod tests {
category: "Basic Checks".to_string(), category: "Basic Checks".to_string(),
display_text: "First Name is Jane".to_string(), display_text: "First Name is Jane".to_string(),
}; };
let mut result = text_comp_success.compare(&student_data); let mut result = text_comp_success.compare(&student_data);
assert!(result.is_ok()); assert!(result.is_ok());
assert_eq!(result.unwrap(), true); assert_eq!(result.unwrap(), true);
result = text_comp_fail.compare(&student_data); result = text_comp_fail.compare(&student_data);
assert!(result.is_err()); assert!(result.is_err());
} }
+21 -21
View File
@@ -62,7 +62,7 @@ impl Comparison for NumberListComparison {
/// Represents all available comparisons for a text list. All comparisons are comparable to a /// Represents all available comparisons for a text list. All comparisons are comparable to a
/// single value. /// single value.
/// ///
/// For example, using `TextListComparison::Contains` checks if a specific value is contained /// For example, using `TextListComparison::Contains` checks if a specific value is contained
/// in the list. /// in the list.
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
@@ -148,12 +148,12 @@ impl Comparison for TextListComparison {
/// For right now, nested lists should be flattened into a list of a single type before they /// For right now, nested lists should be flattened into a list of a single type before they
/// are compared. The plan is to create a more robust framework that eventually be able to /// are compared. The plan is to create a more robust framework that eventually be able to
/// query specific lists and perform comparisons on them. /// query specific lists and perform comparisons on them.
/// ///
/// For example: /// For example:
/// ``` /// ```
/// NestedListComparison::FlattenToTextList(Box::new(TextListComparison::Contains)); /// NestedListComparison::FlattenToTextList(Box::new(TextListComparison::Contains));
/// ``` /// ```
/// ///
/// This will flatten a nested list into a single list of `ValueType::String` enums and then /// This will flatten a nested list into a single list of `ValueType::String` enums and then
/// will check if the list contains the indicated target value. /// will check if the list contains the indicated target value.
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
@@ -288,10 +288,12 @@ impl Comparison for MapListComparison {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::super::test_utils::create_map_list_helper;
use super::*; use super::*;
use super::super::test_utils::{create_map_list_helper}; use crate::common::ComparisonType;
use crate::common::{ComparisonType}; use crate::common::comparison::test_utils::{
use crate::common::comparison::test_utils::{create_empty_lists, create_number_list, create_text_list}; create_empty_lists, create_number_list, create_text_list,
};
#[test] #[test]
fn map_list_flatten_to_text_list() { fn map_list_flatten_to_text_list() {
@@ -415,61 +417,59 @@ mod tests {
assert_eq!(incorrect_value.unwrap(), false); assert_eq!(incorrect_value.unwrap(), false);
assert_eq!(mixed_incorrect_type.unwrap(), false); assert_eq!(mixed_incorrect_type.unwrap(), false);
} }
#[test] #[test]
fn text_list_contains() { fn text_list_contains() {
let (none_list, empty_list) = create_empty_lists(); let (none_list, empty_list) = create_empty_lists();
let num_list = create_number_list(); let num_list = create_number_list();
let text_list = create_text_list(); let text_list = create_text_list();
// Define comparisons here // Define comparisons here
let comparison = ComparisonType::TextList(TextListComparison::Contains); let comparison = ComparisonType::TextList(TextListComparison::Contains);
let target_value = ValueType::String(Some("one".to_string())); let target_value = ValueType::String(Some("one".to_string()));
let none_result = comparison.evaluate(&none_list, &target_value); let none_result = comparison.evaluate(&none_list, &target_value);
let empty_result = comparison.evaluate(&empty_list, &target_value); let empty_result = comparison.evaluate(&empty_list, &target_value);
let num_result = comparison.evaluate(&num_list, &target_value); let num_result = comparison.evaluate(&num_list, &target_value);
let text_list = comparison.evaluate(&text_list, &target_value); let text_list = comparison.evaluate(&text_list, &target_value);
assert_eq!(none_result.unwrap(), false); assert_eq!(none_result.unwrap(), false);
assert_eq!(empty_result.unwrap(), false); assert_eq!(empty_result.unwrap(), false);
assert_eq!(num_result.unwrap(), false); assert_eq!(num_result.unwrap(), false);
assert_eq!(text_list.unwrap(), true); assert_eq!(text_list.unwrap(), true);
} }
#[test] #[test]
fn text_list_empty() { fn text_list_empty() {
let none_list = ValueType::List(None); let none_list = ValueType::List(None);
let empty_list = ValueType::List(Some(vec![])); let empty_list = ValueType::List(Some(vec![]));
let long_list = ValueType::List(Some(vec![ let long_list = ValueType::List(Some(vec![ValueType::String(None)]));
ValueType::String(None),
]));
let empty_comp = ComparisonType::TextList(TextListComparison::IsEmpty); let empty_comp = ComparisonType::TextList(TextListComparison::IsEmpty);
let not_empty_comp = ComparisonType::TextList(TextListComparison::IsNotEmpty); let not_empty_comp = ComparisonType::TextList(TextListComparison::IsNotEmpty);
// This comparison does not use a target value, as it should just check if the list // This comparison does not use a target value, as it should just check if the list
// either doesn't exist or is empty. // either doesn't exist or is empty.
let mut result = empty_comp.evaluate(&empty_list, &ValueType::List(None)); let mut result = empty_comp.evaluate(&empty_list, &ValueType::List(None));
assert!(result.is_ok()); assert!(result.is_ok());
assert_eq!(result.unwrap(), true); assert_eq!(result.unwrap(), true);
result = empty_comp.evaluate(&none_list, &ValueType::List(None)); result = empty_comp.evaluate(&none_list, &ValueType::List(None));
assert!(result.is_ok()); assert!(result.is_ok());
assert_eq!(result.unwrap(), true); assert_eq!(result.unwrap(), true);
result = empty_comp.evaluate(&long_list, &ValueType::List(None)); result = empty_comp.evaluate(&long_list, &ValueType::List(None));
assert!(result.is_ok()); assert!(result.is_ok());
assert_eq!(result.unwrap(), false); assert_eq!(result.unwrap(), false);
result = not_empty_comp.evaluate(&empty_list, &ValueType::List(None)); result = not_empty_comp.evaluate(&empty_list, &ValueType::List(None));
assert!(result.is_ok()); assert!(result.is_ok());
assert_eq!(result.unwrap(), false); assert_eq!(result.unwrap(), false);
result = not_empty_comp.evaluate(&none_list, &ValueType::List(None)); result = not_empty_comp.evaluate(&none_list, &ValueType::List(None));
assert!(result.is_ok()); assert!(result.is_ok());
assert_eq!(result.unwrap(), false); assert_eq!(result.unwrap(), false);
result = not_empty_comp.evaluate(&long_list, &ValueType::List(None)); result = not_empty_comp.evaluate(&long_list, &ValueType::List(None));
assert!(result.is_ok()); assert!(result.is_ok());
assert_eq!(result.unwrap(), true); assert_eq!(result.unwrap(), true);
+20 -21
View File
@@ -9,8 +9,8 @@ pub use self::{base::*, lists::*, number::*, text::*, traits::*};
#[cfg(test)] #[cfg(test)]
mod test_utils { mod test_utils {
use std::collections::HashMap;
use crate::common::{ExpandableInfo, ValueType}; use crate::common::{ExpandableInfo, ValueType};
use std::collections::HashMap;
pub fn create_student_data() -> ExpandableInfo { pub fn create_student_data() -> ExpandableInfo {
let map = create_map_helper( let map = create_map_helper(
@@ -40,19 +40,16 @@ mod test_utils {
ValueType::String(Some(10.to_string())), ValueType::String(Some(10.to_string())),
])), ])),
ValueType::String(Some("N/A".to_string())), ValueType::String(Some("N/A".to_string())),
] ],
))), ))),
ValueType::Map(Some(create_map_helper( ValueType::Map(Some(create_map_helper(
vec![ vec!["activity_name".to_string(), "total_hours".to_string()],
"activity_name".to_string(),
"total_hours".to_string(),
],
vec![ vec![
ValueType::String(Some("Raking".to_string())), ValueType::String(Some("Raking".to_string())),
ValueType::Number(Some(30.to_string())), ValueType::Number(Some(30.to_string())),
] ],
))), ))),
] ],
); );
let mut info = ExpandableInfo::new("test"); let mut info = ExpandableInfo::new("test");
@@ -61,9 +58,11 @@ mod test_utils {
info info
} }
pub fn create_map_helper(keys: Vec<String>, values: Vec<ValueType>) -> HashMap<String, ValueType> { pub fn create_map_helper(
keys keys: Vec<String>,
.iter() values: Vec<ValueType>,
) -> HashMap<String, ValueType> {
keys.iter()
.cloned() .cloned()
.zip(values.iter().cloned()) .zip(values.iter().cloned())
.collect::<HashMap<String, ValueType>>() .collect::<HashMap<String, ValueType>>()
@@ -76,7 +75,7 @@ mod test_utils {
"last_name".to_string(), "last_name".to_string(),
"gender".to_string(), "gender".to_string(),
"sat_score".to_string(), "sat_score".to_string(),
"grades_participated".to_string() "grades_participated".to_string(),
], ],
vec![ vec![
ValueType::String(Some("John".to_string())), ValueType::String(Some("John".to_string())),
@@ -85,8 +84,8 @@ mod test_utils {
ValueType::Number(Some(1200.to_string())), ValueType::Number(Some(1200.to_string())),
ValueType::List(Some(vec![ ValueType::List(Some(vec![
ValueType::String(Some("9".to_string())), ValueType::String(Some("9".to_string())),
ValueType::String(Some("10".to_string())) ValueType::String(Some("10".to_string())),
])) ])),
], ],
); );
@@ -96,7 +95,7 @@ mod test_utils {
"last_name".to_string(), "last_name".to_string(),
"gender".to_string(), "gender".to_string(),
"sat_score".to_string(), "sat_score".to_string(),
"grades_participated".to_string() "grades_participated".to_string(),
], ],
vec![ vec![
ValueType::String(Some("Jane".to_string())), ValueType::String(Some("Jane".to_string())),
@@ -105,8 +104,8 @@ mod test_utils {
ValueType::Number(Some(1000.to_string())), ValueType::Number(Some(1000.to_string())),
ValueType::List(Some(vec![ ValueType::List(Some(vec![
ValueType::String(Some("11".to_string())), ValueType::String(Some("11".to_string())),
ValueType::String(Some("12".to_string())) ValueType::String(Some("12".to_string())),
])) ])),
], ],
); );
@@ -115,14 +114,14 @@ mod test_utils {
ValueType::Map(Some(map_two)), ValueType::Map(Some(map_two)),
])) ]))
} }
pub fn create_empty_lists() -> (ValueType, ValueType) { pub fn create_empty_lists() -> (ValueType, ValueType) {
let none_list = ValueType::List(None); let none_list = ValueType::List(None);
let empty_list = ValueType::List(Some(vec![])); let empty_list = ValueType::List(Some(vec![]));
(none_list, empty_list) (none_list, empty_list)
} }
pub fn create_number_list() -> ValueType { pub fn create_number_list() -> ValueType {
ValueType::List(Some(vec![ ValueType::List(Some(vec![
ValueType::Number(Some(1.to_string())), ValueType::Number(Some(1.to_string())),
@@ -132,7 +131,7 @@ mod test_utils {
ValueType::Number(Some(5.to_string())), ValueType::Number(Some(5.to_string())),
])) ]))
} }
pub fn create_text_list() -> ValueType { pub fn create_text_list() -> ValueType {
ValueType::List(Some(vec![ ValueType::List(Some(vec![
ValueType::String(Some("one".to_string())), ValueType::String(Some("one".to_string())),
+4 -2
View File
@@ -1,8 +1,10 @@
mod comparison; mod comparison;
mod expandable_info; mod expandable_info;
mod form_params;
mod form_schema; mod form_schema;
mod user; mod user;
mod value_type; mod value_type;
mod form_params;
pub use self::{comparison::*, expandable_info::*, form_schema::*, user::*, value_type::*, form_params::*}; pub use self::{
comparison::*, expandable_info::*, form_params::*, form_schema::*, user::*, value_type::*,
};
+2 -1
View File
@@ -74,7 +74,8 @@ pub fn MultiEntry(
#[prop()] #[prop()]
data_map: RwSignal<HashMap<String, ValueType>>, data_map: RwSignal<HashMap<String, ValueType>>,
/// Currently unused. /// Currently unused.
#[prop(optional, into)] name_member: String, #[prop(optional, into)]
name_member: String,
/// The type and order of the inputs for each `Entry` component within this `MultiEntry` component. /// The type and order of the inputs for each `Entry` component within this `MultiEntry` component.
#[prop(optional)] #[prop(optional)]
schema: Vec<InputType>, schema: Vec<InputType>,
+70 -69
View File
@@ -2,189 +2,190 @@
use crate::pages::utils::server_utils::create_dynamo_client; use crate::pages::utils::server_utils::create_dynamo_client;
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
use aws_sdk_dynamodb::{ use aws_sdk_dynamodb::error::ProvideErrorMetadata;
error::ProvideErrorMetadata,
};
use crate::common::{
ComparisonData, ComparisonType, MapListComparison, NumberComparison, NumberListComparison,
TextComparison, TextListComparison, ValueType,
};
use leptos::leptos_dom::log; use leptos::leptos_dom::log;
use leptos::prelude::ServerFnError; use leptos::prelude::ServerFnError;
use leptos::server; use leptos::server;
use crate::common::{ComparisonData, ComparisonType, MapListComparison, NumberComparison, NumberListComparison, TextComparison, TextListComparison, ValueType};
/// Helper function to create all sports comparisons. Only used to create initial /// Helper function to create all sports comparisons. Only used to create initial
/// comparison data, and should be removed upon completion of a comparison editor page. /// comparison data, and should be removed upon completion of a comparison editor page.
fn create_sports_comparisons() -> Vec<ComparisonData> { fn create_sports_comparisons() -> Vec<ComparisonData> {
vec! [ vec![
ComparisonData::new( ComparisonData::new(
"sports_football", "sports_football",
"sports_participation", "sports_participation",
ComparisonType::MapList(MapListComparison::FlattenToTextList( ComparisonType::MapList(MapListComparison::FlattenToTextList(
"sport_name".to_string(), "sport_name".to_string(),
Box::new(TextListComparison::Contains) Box::new(TextListComparison::Contains),
)), )),
ValueType::String(Some("Football".to_string())), ValueType::String(Some("Football".to_string())),
"Sports Participation", "Sports Participation",
"Football" "Football",
), ),
ComparisonData::new( ComparisonData::new(
"sports_soccer", "sports_soccer",
"sports_participation", "sports_participation",
ComparisonType::MapList(MapListComparison::FlattenToTextList( ComparisonType::MapList(MapListComparison::FlattenToTextList(
"sport_name".to_string(), "sport_name".to_string(),
Box::new(TextListComparison::Contains) Box::new(TextListComparison::Contains),
)), )),
ValueType::String(Some("Soccer".to_string())), ValueType::String(Some("Soccer".to_string())),
"Sports Participation", "Sports Participation",
"Soccer" "Soccer",
), ),
ComparisonData::new( ComparisonData::new(
"sports_cheerleading", "sports_cheerleading",
"sports_participation", "sports_participation",
ComparisonType::MapList(MapListComparison::FlattenToTextList( ComparisonType::MapList(MapListComparison::FlattenToTextList(
"sport_name".to_string(), "sport_name".to_string(),
Box::new(TextListComparison::Contains) Box::new(TextListComparison::Contains),
)), )),
ValueType::String(Some("Cheerleading".to_string())), ValueType::String(Some("Cheerleading".to_string())),
"Sports Participation", "Sports Participation",
"Cheerleading" "Cheerleading",
), ),
ComparisonData::new( ComparisonData::new(
"sports_field_hockey", "sports_field_hockey",
"sports_participation", "sports_participation",
ComparisonType::MapList(MapListComparison::FlattenToTextList( ComparisonType::MapList(MapListComparison::FlattenToTextList(
"sport_name".to_string(), "sport_name".to_string(),
Box::new(TextListComparison::Contains) Box::new(TextListComparison::Contains),
)), )),
ValueType::String(Some("Field Hockey".to_string())), ValueType::String(Some("Field Hockey".to_string())),
"Sports Participation", "Sports Participation",
"Field Hockey" "Field Hockey",
), ),
ComparisonData::new( ComparisonData::new(
"sports_swimming", "sports_swimming",
"sports_participation", "sports_participation",
ComparisonType::MapList(MapListComparison::FlattenToTextList( ComparisonType::MapList(MapListComparison::FlattenToTextList(
"sport_name".to_string(), "sport_name".to_string(),
Box::new(TextListComparison::Contains) Box::new(TextListComparison::Contains),
)), )),
ValueType::String(Some("Swimming".to_string())), ValueType::String(Some("Swimming".to_string())),
"Sports Participation", "Sports Participation",
"Swimming" "Swimming",
), ),
ComparisonData::new( ComparisonData::new(
"sports_golf", "sports_golf",
"sports_participation", "sports_participation",
ComparisonType::MapList(MapListComparison::FlattenToTextList( ComparisonType::MapList(MapListComparison::FlattenToTextList(
"sport_name".to_string(), "sport_name".to_string(),
Box::new(TextListComparison::Contains) Box::new(TextListComparison::Contains),
)), )),
ValueType::String(Some("Golf".to_string())), ValueType::String(Some("Golf".to_string())),
"Sports Participation", "Sports Participation",
"Golf" "Golf",
), ),
ComparisonData::new( ComparisonData::new(
"sports_basketball", "sports_basketball",
"sports_participation", "sports_participation",
ComparisonType::MapList(MapListComparison::FlattenToTextList( ComparisonType::MapList(MapListComparison::FlattenToTextList(
"sport_name".to_string(), "sport_name".to_string(),
Box::new(TextListComparison::Contains) Box::new(TextListComparison::Contains),
)), )),
ValueType::String(Some("Basketball".to_string())), ValueType::String(Some("Basketball".to_string())),
"Sports Participation", "Sports Participation",
"Basketball" "Basketball",
), ),
ComparisonData::new( ComparisonData::new(
"sports_track", "sports_track",
"sports_participation", "sports_participation",
ComparisonType::MapList(MapListComparison::FlattenToTextList( ComparisonType::MapList(MapListComparison::FlattenToTextList(
"sport_name".to_string(), "sport_name".to_string(),
Box::new(TextListComparison::Contains) Box::new(TextListComparison::Contains),
)), )),
ValueType::String(Some("Track".to_string())), ValueType::String(Some("Track".to_string())),
"Sports Participation", "Sports Participation",
"Track" "Track",
), ),
ComparisonData::new( ComparisonData::new(
"sports_gymnastics", "sports_gymnastics",
"sports_participation", "sports_participation",
ComparisonType::MapList(MapListComparison::FlattenToTextList( ComparisonType::MapList(MapListComparison::FlattenToTextList(
"sport_name".to_string(), "sport_name".to_string(),
Box::new(TextListComparison::Contains) Box::new(TextListComparison::Contains),
)), )),
ValueType::String(Some("Gymnastics".to_string())), ValueType::String(Some("Gymnastics".to_string())),
"Sports Participation", "Sports Participation",
"Gymnastics" "Gymnastics",
), ),
ComparisonData::new( ComparisonData::new(
"sports_ice_hockey", "sports_ice_hockey",
"sports_participation", "sports_participation",
ComparisonType::MapList(MapListComparison::FlattenToTextList( ComparisonType::MapList(MapListComparison::FlattenToTextList(
"sport_name".to_string(), "sport_name".to_string(),
Box::new(TextListComparison::Contains) Box::new(TextListComparison::Contains),
)), )),
ValueType::String(Some("Ice Hockey".to_string())), ValueType::String(Some("Ice Hockey".to_string())),
"Sports Participation", "Sports Participation",
"Ice Hockey" "Ice Hockey",
), ),
ComparisonData::new( ComparisonData::new(
"sports_ski", "sports_ski",
"sports_participation", "sports_participation",
ComparisonType::MapList(MapListComparison::FlattenToTextList( ComparisonType::MapList(MapListComparison::FlattenToTextList(
"sport_name".to_string(), "sport_name".to_string(),
Box::new(TextListComparison::Contains) Box::new(TextListComparison::Contains),
)), )),
ValueType::String(Some("Ski".to_string())), ValueType::String(Some("Ski".to_string())),
"Sports Participation", "Sports Participation",
"Ski" "Ski",
), ),
ComparisonData::new( ComparisonData::new(
"sports_wrestling", "sports_wrestling",
"sports_participation", "sports_participation",
ComparisonType::MapList(MapListComparison::FlattenToTextList( ComparisonType::MapList(MapListComparison::FlattenToTextList(
"sport_name".to_string(), "sport_name".to_string(),
Box::new(TextListComparison::Contains) Box::new(TextListComparison::Contains),
)), )),
ValueType::String(Some("Wrestling".to_string())), ValueType::String(Some("Wrestling".to_string())),
"Sports Participation", "Sports Participation",
"Wrestling" "Wrestling",
), ),
ComparisonData::new( ComparisonData::new(
"sports_lacrosse", "sports_lacrosse",
"sports_participation", "sports_participation",
ComparisonType::MapList(MapListComparison::FlattenToTextList( ComparisonType::MapList(MapListComparison::FlattenToTextList(
"sport_name".to_string(), "sport_name".to_string(),
Box::new(TextListComparison::Contains) Box::new(TextListComparison::Contains),
)), )),
ValueType::String(Some("Lacrosse".to_string())), ValueType::String(Some("Lacrosse".to_string())),
"Sports Participation", "Sports Participation",
"Lacrosse" "Lacrosse",
), ),
ComparisonData::new( ComparisonData::new(
"sports_softball", "sports_softball",
"sports_participation", "sports_participation",
ComparisonType::MapList(MapListComparison::FlattenToTextList( ComparisonType::MapList(MapListComparison::FlattenToTextList(
"sport_name".to_string(), "sport_name".to_string(),
Box::new(TextListComparison::Contains) Box::new(TextListComparison::Contains),
)), )),
ValueType::String(Some("Softball".to_string())), ValueType::String(Some("Softball".to_string())),
"Sports Participation", "Sports Participation",
"Softball" "Softball",
), ),
ComparisonData::new( ComparisonData::new(
"sports_tennis", "sports_tennis",
"sports_participation", "sports_participation",
ComparisonType::MapList(MapListComparison::FlattenToTextList( ComparisonType::MapList(MapListComparison::FlattenToTextList(
"sport_name".to_string(), "sport_name".to_string(),
Box::new(TextListComparison::Contains) Box::new(TextListComparison::Contains),
)), )),
ValueType::String(Some("Tennis".to_string())), ValueType::String(Some("Tennis".to_string())),
"Sports Participation", "Sports Participation",
"Tennis" "Tennis",
) ),
] ]
} }
#[server(CreateTestComparisons, endpoint = "/comparisons/create-test")] #[server(CreateTestComparisons, endpoint = "/comparisons/create-test")]
pub async fn create_test_comparisons() -> Result<(), ServerFnError> { pub async fn create_test_comparisons() -> Result<(), ServerFnError> {
let client = create_dynamo_client().await; let client = create_dynamo_client().await;
log!("Creating test comparisons"); log!("Creating test comparisons");
@@ -195,96 +196,96 @@ pub async fn create_test_comparisons() -> Result<(), ServerFnError> {
ComparisonType::Number(NumberComparison::GreaterThanOrEqual), ComparisonType::Number(NumberComparison::GreaterThanOrEqual),
ValueType::Number(Some(650.to_string())), ValueType::Number(Some(650.to_string())),
"Academic Information", "Academic Information",
"Math SAT Score > 650" "Math SAT Score > 650",
); );
let service_hours_25 = ComparisonData::new( let service_hours_25 = ComparisonData::new(
"map_sum_comp", "map_sum_comp",
"community_involvement", "community_involvement",
ComparisonType::MapList(MapListComparison::FlattenToNumberList( ComparisonType::MapList(MapListComparison::FlattenToNumberList(
"service_hours".to_string(), "service_hours".to_string(),
Box::new(NumberListComparison::Sum( Box::new(NumberListComparison::Sum(Box::new(
Box::new(NumberComparison::GreaterThanOrEqual) NumberComparison::GreaterThanOrEqual,
)) ))),
)), )),
ValueType::Number(Some(25.to_string())), ValueType::Number(Some(25.to_string())),
"Community Involvement", "Community Involvement",
"25 service hours" "25 service hours",
); );
let service_hours_20 = ComparisonData::new( let service_hours_20 = ComparisonData::new(
"service_hours_20", "service_hours_20",
"community_involvement", "community_involvement",
ComparisonType::MapList(MapListComparison::FlattenToNumberList( ComparisonType::MapList(MapListComparison::FlattenToNumberList(
"service_hours".to_string(), "service_hours".to_string(),
Box::new(NumberListComparison::Sum( Box::new(NumberListComparison::Sum(Box::new(
Box::new(NumberComparison::GreaterThanOrEqual) NumberComparison::GreaterThanOrEqual,
)) ))),
)), )),
ValueType::Number(Some(20.to_string())), ValueType::Number(Some(20.to_string())),
"Community Involvement", "Community Involvement",
"20 service hours" "20 service hours",
); );
let service_hours_30 = ComparisonData::new( let service_hours_30 = ComparisonData::new(
"service_hours_30", "service_hours_30",
"community_involvement", "community_involvement",
ComparisonType::MapList(MapListComparison::FlattenToNumberList( ComparisonType::MapList(MapListComparison::FlattenToNumberList(
"service_hours".to_string(), "service_hours".to_string(),
Box::new(NumberListComparison::Sum( Box::new(NumberListComparison::Sum(Box::new(
Box::new(NumberComparison::GreaterThanOrEqual) NumberComparison::GreaterThanOrEqual,
)) ))),
)), )),
ValueType::Number(Some(30.to_string())), ValueType::Number(Some(30.to_string())),
"Community Involvement", "Community Involvement",
"30 service hours" "30 service hours",
); );
let residency_southbury = ComparisonData::new( let residency_southbury = ComparisonData::new(
"residency_southbury", "residency_southbury",
"town", "town",
ComparisonType::Text(TextComparison::Matches), ComparisonType::Text(TextComparison::Matches),
ValueType::String(Some("Southbury".to_string())), ValueType::String(Some("Southbury".to_string())),
"Residency", "Residency",
"Southbury" "Southbury",
); );
let residency_middlebury = ComparisonData::new( let residency_middlebury = ComparisonData::new(
"residency_middlebury", "residency_middlebury",
"town", "town",
ComparisonType::Text(TextComparison::Matches), ComparisonType::Text(TextComparison::Matches),
ValueType::String(Some("Middlebury".to_string())), ValueType::String(Some("Middlebury".to_string())),
"Residency", "Residency",
"Middlebury" "Middlebury",
); );
let attended_bas = ComparisonData::new( let attended_bas = ComparisonData::new(
"attended_bas", "attended_bas",
"attended_bas", "attended_bas",
ComparisonType::Text(TextComparison::Matches), ComparisonType::Text(TextComparison::Matches),
ValueType::String(Some("Yes".to_string())), ValueType::String(Some("Yes".to_string())),
"Miscellaneous", "Miscellaneous",
"Attended BAS" "Attended BAS",
); );
let midd_south = ComparisonData::new( let midd_south = ComparisonData::new(
"midd_south", "midd_south",
"middsouth_church", "middsouth_church",
ComparisonType::Text(TextComparison::Matches), ComparisonType::Text(TextComparison::Matches),
ValueType::String(Some("Yes".to_string())), ValueType::String(Some("Yes".to_string())),
"Miscellaneous", "Miscellaneous",
"Member of Midd-South Church" "Member of Midd-South Church",
); );
let family_military = ComparisonData::new( let family_military = ComparisonData::new(
"family_military", "family_military",
"family_military_service", "family_military_service",
ComparisonType::Text(TextComparison::Matches), ComparisonType::Text(TextComparison::Matches),
ValueType::String(Some("Yes".to_string())), ValueType::String(Some("Yes".to_string())),
"Miscellaneous", "Miscellaneous",
"Family with Military Service" "Family with Military Service",
); );
let mut sports_comps = create_sports_comparisons(); let mut sports_comps = create_sports_comparisons();
let mut major_comps = Vec::<ComparisonData>::new(); let mut major_comps = Vec::<ComparisonData>::new();
@@ -299,7 +300,7 @@ pub async fn create_test_comparisons() -> Result<(), ServerFnError> {
midd_south, midd_south,
family_military, family_military,
]; ];
comp_list.append(&mut sports_comps); comp_list.append(&mut sports_comps);
comp_list.append(&mut major_comps); comp_list.append(&mut major_comps);
@@ -309,13 +310,13 @@ pub async fn create_test_comparisons() -> Result<(), ServerFnError> {
.table_name("leptos-comparison-test") .table_name("leptos-comparison-test")
.set_item(Some(serde_dynamo::to_item(&comparison)?)) .set_item(Some(serde_dynamo::to_item(&comparison)?))
.send() .send()
.await .await
{ {
let msg = err.message().unwrap_or("An unknown error occurred"); let msg = err.message().unwrap_or("An unknown error occurred");
return Err(ServerFnError::new(msg)) return Err(ServerFnError::new(msg));
} }
} }
Ok(()) Ok(())
} }
@@ -340,7 +341,7 @@ pub async fn get_comparison_info() -> Result<Vec<ComparisonData>, ServerFnError>
} else { } else {
Ok(vec![]) Ok(vec![])
} }
}, }
Err(err) => { Err(err) => {
let msg = err.message().unwrap_or("An unknown error occurred"); let msg = err.message().unwrap_or("An unknown error occurred");
Err(ServerFnError::new(msg)) Err(ServerFnError::new(msg))
+1 -1
View File
@@ -1,4 +1,4 @@
mod comparisons; mod comparisons;
mod scholarships; mod scholarships;
pub use self::{comparisons::*, scholarships::*}; pub use self::{comparisons::*, scholarships::*};
+7 -7
View File
@@ -1,13 +1,10 @@
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
use aws_sdk_dynamodb::{ use aws_sdk_dynamodb::{error::ProvideErrorMetadata, types::AttributeValue};
types::AttributeValue,
error::ProvideErrorMetadata,
};
use crate::common::ExpandableInfo;
use leptos::leptos_dom::log; use leptos::leptos_dom::log;
use leptos::prelude::ServerFnError; use leptos::prelude::ServerFnError;
use leptos::server; use leptos::server;
use crate::common::ExpandableInfo;
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
use crate::pages::utils::server_utils::create_dynamo_client; use crate::pages::utils::server_utils::create_dynamo_client;
@@ -49,7 +46,10 @@ pub async fn create_scholarship_info(info: ExpandableInfo) -> Result<(), ServerF
let client = create_dynamo_client().await; let client = create_dynamo_client().await;
log!("Creating or updating scholarship with ID {:?}", info.subject); log!(
"Creating or updating scholarship with ID {:?}",
info.subject
);
match client match client
.put_item() .put_item()
@@ -82,7 +82,7 @@ pub async fn get_all_scholarship_info() -> Result<Vec<ExpandableInfo>, ServerFnE
} else { } else {
Ok(Vec::new()) Ok(Vec::new())
} }
}, }
Err(err) => { Err(err) => {
let msg = err.message().unwrap_or("An unknown error occurred"); let msg = err.message().unwrap_or("An unknown error occurred");
Err(ServerFnError::new(msg)) Err(ServerFnError::new(msg))
+23 -27
View File
@@ -1,15 +1,13 @@
use leptos::either::Either; use super::UnauthenticatedPage;
use super::api::{get_all_scholarship_info, get_comparison_info};
use super::home_page::get_submission; use super::home_page::get_submission;
use super::{UnauthenticatedPage}; use crate::common::{ComparisonData, ExpandableInfo, UserClaims, ValueType};
use crate::common::{
ComparisonData, ExpandableInfo, UserClaims, ValueType,
};
use crate::components::{ActionButton, Loading, Panel, Row}; use crate::components::{ActionButton, Loading, Panel, Row};
use leptos::either::Either;
use leptos::leptos_dom::logging::console_log; use leptos::leptos_dom::logging::console_log;
use leptos::logging::log; use leptos::logging::log;
use leptos::prelude::*; use leptos::prelude::*;
use leptos_oidc::{Algorithm, AuthLoaded, AuthSignal, Authenticated}; use leptos_oidc::{Algorithm, AuthLoaded, AuthSignal, Authenticated};
use super::api::{get_comparison_info, get_all_scholarship_info};
#[component] #[component]
pub fn ComparisonTestPage() -> impl IntoView { pub fn ComparisonTestPage() -> impl IntoView {
@@ -28,17 +26,15 @@ pub fn ComparisonTestPage() -> impl IntoView {
// we don't lose access to it in the future, should we need it again. // we don't lose access to it in the future, should we need it again.
let student_resource: Resource<ExpandableInfo> = Resource::new( let student_resource: Resource<ExpandableInfo> = Resource::new(
move || user_claims.get().map(|claim| claim.claims.subject.clone()), move || user_claims.get().map(|claim| claim.claims.subject.clone()),
async async |opt_username| match opt_username {
| opt_username Some(subject) => get_submission(subject.clone()).await.unwrap_or_else(|e| {
| match opt_username { console_log(e.to_string().as_str());
Some(subject) => get_submission(subject.clone()).await.unwrap_or_else(|e| { ExpandableInfo::new(subject)
console_log(e.to_string().as_str()); }),
ExpandableInfo::new(subject) None => ExpandableInfo::new("".to_owned()),
}), },
None => ExpandableInfo::new("".to_owned()),
},
); );
let scholarship_resource: Resource<Vec<ExpandableInfo>> = Resource::new( let scholarship_resource: Resource<Vec<ExpandableInfo>> = Resource::new(
move || Option::<String>::None, move || Option::<String>::None,
async |_| { async |_| {
@@ -46,9 +42,9 @@ pub fn ComparisonTestPage() -> impl IntoView {
log!("An error occurred: {:?}", e); log!("An error occurred: {:?}", e);
Vec::new() Vec::new()
}) })
} },
); );
let comparison_resource: Resource<Vec<ComparisonData>> = Resource::new( let comparison_resource: Resource<Vec<ComparisonData>> = Resource::new(
move || Option::<String>::None, move || Option::<String>::None,
async |_| { async |_| {
@@ -56,7 +52,7 @@ pub fn ComparisonTestPage() -> impl IntoView {
log!("An error occurred: {:?}", e); log!("An error occurred: {:?}", e);
Vec::new() Vec::new()
}) })
} },
); );
view! { view! {
@@ -89,9 +85,9 @@ pub fn ComparisonTestPage() -> impl IntoView {
return Ok(Some(scholarship)) // students will always qualify when there are no requirements return Ok(Some(scholarship)) // students will always qualify when there are no requirements
} }
}; };
let mut final_result = Ok(true); let mut final_result = Ok(true);
// The list should exist, and it should contain a series of strings. // The list should exist, and it should contain a series of strings.
for val in list { for val in list {
let comp_opt = match val { let comp_opt = match val {
@@ -101,7 +97,7 @@ pub fn ComparisonTestPage() -> impl IntoView {
} }
_ => None _ => None
}; };
let comparison_outcome = match comp_opt { let comparison_outcome = match comp_opt {
Some(comp) => { Some(comp) => {
// Perform the comparison // Perform the comparison
@@ -109,7 +105,7 @@ pub fn ComparisonTestPage() -> impl IntoView {
} }
_ => Err("".to_string()) _ => Err("".to_string())
}; };
match comparison_outcome { match comparison_outcome {
Ok(true) => {}, Ok(true) => {},
Ok(false) => final_result = Ok(false), Ok(false) => final_result = Ok(false),
@@ -119,14 +115,14 @@ pub fn ComparisonTestPage() -> impl IntoView {
} }
} }
} }
match final_result { match final_result {
Ok(true) => Ok(Some(scholarship)), Ok(true) => Ok(Some(scholarship)),
Ok(false) => Ok(None), Ok(false) => Ok(None),
Err(e) => Err(e) Err(e) => Err(e)
} }
}).collect::<Vec<Result<Option<&ExpandableInfo>, String>>>(); }).collect::<Vec<Result<Option<&ExpandableInfo>, String>>>();
// Build a display string // Build a display string
let mut strings = Vec::new(); let mut strings = Vec::new();
strings.push("Scholarship results: \n".to_string()); strings.push("Scholarship results: \n".to_string());
@@ -143,9 +139,9 @@ pub fn ComparisonTestPage() -> impl IntoView {
Err(msg) => strings.push(msg) Err(msg) => strings.push(msg)
} }
} }
let display_text = strings.join("\n").to_string(); let display_text = strings.join("\n").to_string();
result_msg.set(display_text); result_msg.set(display_text);
}>"Test Comparison"</ActionButton> }>"Test Comparison"</ActionButton>
</Row> </Row>
+10 -12
View File
@@ -5,24 +5,24 @@ use aws_sdk_dynamodb::{Client, error::ProvideErrorMetadata, types::AttributeValu
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
use serde_dynamo::{from_item, to_item}; use serde_dynamo::{from_item, to_item};
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
use std::process::{Command, Stdio};
#[cfg(feature = "ssr")]
use std::io::Write; use std::io::Write;
#[cfg(feature = "ssr")]
use std::process::{Command, Stdio};
use crate::common::{ExpandableInfo, UserClaims}; use crate::common::{ExpandableInfo, UserClaims};
use crate::pages::UnauthenticatedPage;
use crate::components::{ use crate::components::{
ActionButton, CheckboxList, Loading, MultiEntry, OutlinedTextField, Panel, RadioList, Row, ActionButton, CheckboxList, Loading, MultiEntry, OutlinedTextField, Panel, RadioList, Row,
Select Select,
}; };
use crate::input; use crate::input;
use crate::pages::UnauthenticatedPage;
use base64::Engine;
use leptos::leptos_dom::logging::console_log; use leptos::leptos_dom::logging::console_log;
use leptos::prelude::*; use leptos::prelude::*;
use leptos_oidc::{Algorithm, AuthLoaded, AuthSignal, Authenticated};
use base64::Engine;
use leptos::task::spawn_local; use leptos::task::spawn_local;
use leptos::web_sys::HtmlAnchorElement;
use leptos::wasm_bindgen::JsCast; use leptos::wasm_bindgen::JsCast;
use leptos::web_sys::HtmlAnchorElement;
use leptos_oidc::{Algorithm, AuthLoaded, AuthSignal, Authenticated};
use traits::{AsReactive, ReactiveCapture}; use traits::{AsReactive, ReactiveCapture};
/// # Get Student Info /// # Get Student Info
@@ -125,16 +125,14 @@ pub async fn create_example_pdf() -> Result<Vec<u8>, ServerFnError> {
let mut command = Command::new("typst"); let mut command = Command::new("typst");
command command
.arg("compile") .arg("compile")
.arg("-") // Use stdin as input .arg("-") // Use stdin as input
.arg("-") // Write output to stdout .arg("-") // Write output to stdout
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stdout(Stdio::piped()); .stdout(Stdio::piped());
let mut process = command.spawn()?; let mut process = command.spawn()?;
process.stdin process.stdin.take().unwrap().write(template.as_bytes())?;
.take().unwrap()
.write(template.as_bytes())?;
let output = process.wait_with_output()?; let output = process.wait_with_output()?;
let pdf_bytes = output.stdout; let pdf_bytes = output.stdout;
+81 -48
View File
@@ -4,23 +4,25 @@ use aws_sdk_dynamodb::{Client, error::ProvideErrorMetadata, types::AttributeValu
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
use serde_dynamo::{from_item, to_item}; use serde_dynamo::{from_item, to_item};
use leptos::either::{Either, EitherOf3};
use leptos::prelude::*;
use serde::{Deserialize, Serialize};
use leptos::logging::log;
use leptos_router::params::Params;
use leptos_router::hooks::{use_navigate, use_params};
use leptos::Params;
use traits::{AsReactive, ReactiveCapture};
use crate::common::{ExpandableInfo, ValueType}; use crate::common::{ExpandableInfo, ValueType};
use crate::components::{ActionButton, Banner, DashboardButton, OutlinedTextField, Panel, Row, Select}; use crate::components::{
use std::collections::HashMap; ActionButton, Banner, DashboardButton, OutlinedTextField, Panel, Row, Select,
};
use chrono::{FixedOffset, TimeZone}; use chrono::{FixedOffset, TimeZone};
use leptos::Params;
use leptos::either::{Either, EitherOf3};
use leptos::html::Dialog; use leptos::html::Dialog;
use leptos::logging::log;
use leptos::prelude::*;
use leptos_router::hooks::{use_navigate, use_params};
use leptos_router::params::Params;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use traits::{AsReactive, ReactiveCapture};
#[derive(Params, PartialEq)] #[derive(Params, PartialEq)]
struct LoanerParams { struct LoanerParams {
form_name: Option<String> form_name: Option<String>,
} }
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
@@ -39,7 +41,8 @@ struct LoanerReturnOutput {
async fn get_loaners_return_list() -> Result<Vec<LoanerReturnOutput>, ServerFnError> { async fn get_loaners_return_list() -> Result<Vec<LoanerReturnOutput>, ServerFnError> {
// We want to query the database for all loaners, but we only want the name and // We want to query the database for all loaners, but we only want the name and
// date of borrowing. This will be cheaper than querying the entire table. // date of borrowing. This will be cheaper than querying the entire table.
let dbclient = Client::new(&aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await); let dbclient =
Client::new(&aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await);
log!("Getting loaners from database."); log!("Getting loaners from database.");
@@ -48,34 +51,57 @@ async fn get_loaners_return_list() -> Result<Vec<LoanerReturnOutput>, ServerFnEr
.table_name("loaner-info") .table_name("loaner-info")
.projection_expression("subject, first_name, last_name, date_taken") .projection_expression("subject, first_name, last_name, date_taken")
.send() .send()
.await { .await
{
Ok(output) => { Ok(output) => {
if let Some(items) = output.items { if let Some(items) = output.items {
Ok(items.into_iter().map(|item| { Ok(items
let expandable: ExpandableInfo = from_item(item).unwrap(); .into_iter()
// This set of function calls is ridiculous, we need to make a better way to do this. .map(|item| {
LoanerReturnOutput { let expandable: ExpandableInfo = from_item(item).unwrap();
first_name: expandable.data.get("first_name").unwrap().as_string().unwrap_or_default().unwrap_or_default(), // This set of function calls is ridiculous, we need to make a better way to do this.
last_name: expandable.data.get("last_name").unwrap().as_string().unwrap_or_default().unwrap_or_default(), LoanerReturnOutput {
date: expandable.data.get("date_taken").unwrap().as_string().unwrap_or_default().unwrap_or_default(), first_name: expandable
subject: expandable.subject .data
} .get("first_name")
}).collect()) .unwrap()
.as_string()
.unwrap_or_default()
.unwrap_or_default(),
last_name: expandable
.data
.get("last_name")
.unwrap()
.as_string()
.unwrap_or_default()
.unwrap_or_default(),
date: expandable
.data
.get("date_taken")
.unwrap()
.as_string()
.unwrap_or_default()
.unwrap_or_default(),
subject: expandable.subject,
}
})
.collect())
} else { } else {
Ok(vec![]) Ok(vec![])
} }
}, }
Err(err) => { Err(err) => {
log!("{:?}", err); log!("{:?}", err);
let msg = err.message().unwrap_or("Unknown error"); let msg = err.message().unwrap_or("Unknown error");
Err(ServerFnError::new(msg)) Err(ServerFnError::new(msg))
}, }
} }
} }
#[server] #[server]
async fn check_in_loaner(student: LoanerReturnOutput) -> Result<(), ServerFnError> { async fn check_in_loaner(student: LoanerReturnOutput) -> Result<(), ServerFnError> {
let dbclient = Client::new(&aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await); let dbclient =
Client::new(&aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await);
match dbclient match dbclient
.delete_item() .delete_item()
@@ -96,13 +122,17 @@ async fn check_in_loaner(student: LoanerReturnOutput) -> Result<(), ServerFnErro
#[server] #[server]
async fn create_borrow_entry(input: ExpandableInfo) -> Result<(), ServerFnError> { async fn create_borrow_entry(input: ExpandableInfo) -> Result<(), ServerFnError> {
let mut input = input; let mut input = input;
let dbclient = Client::new(&aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await); let dbclient =
Client::new(&aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await);
let current_time = chrono::Utc::now().format("%H:%M, %m/%d/%Y").to_string(); let current_time = chrono::Utc::now().format("%H:%M, %m/%d/%Y").to_string();
log!("Creating entry with timestamp {}", current_time); log!("Creating entry with timestamp {}", current_time);
// Add the timestamp to the data map as a string and then push it to the database. // Add the timestamp to the data map as a string and then push it to the database.
input.data.insert("date_taken".to_string(), ValueType::String(Some(current_time.clone()))); input.data.insert(
"date_taken".to_string(),
ValueType::String(Some(current_time.clone())),
);
loop { loop {
let item: serde_dynamo::Item = to_item(&input)?; let item: serde_dynamo::Item = to_item(&input)?;
@@ -122,8 +152,8 @@ async fn create_borrow_entry(input: ExpandableInfo) -> Result<(), ServerFnError>
// Retry database writes until we have a good subject ID. // Retry database writes until we have a good subject ID.
log!("Failed conditional check, forcing new subject ID."); log!("Failed conditional check, forcing new subject ID.");
input.subject = uuid::Uuid::new_v4().to_string(); input.subject = uuid::Uuid::new_v4().to_string();
continue continue;
}, }
_ => { _ => {
let msg = err.message().unwrap_or("An unknown error occurred."); let msg = err.message().unwrap_or("An unknown error occurred.");
return Err(ServerFnError::new(msg)); return Err(ServerFnError::new(msg));
@@ -165,19 +195,18 @@ pub fn LoanerPage() -> impl IntoView {
// Check URL params. Depending on what string is visible here, we'll display a different form. // Check URL params. Depending on what string is visible here, we'll display a different form.
let params = use_params::<LoanerParams>(); let params = use_params::<LoanerParams>();
let form_passed = move || { let form_passed = move || {
params.read() params
.read()
.as_ref() .as_ref()
.ok() .ok()
.and_then(|params| params.form_name.clone()) .and_then(|params| params.form_name.clone())
.unwrap_or_default() .unwrap_or_default()
}; };
let form_view = move || { let form_view = move || match form_passed().as_str() {
match form_passed().as_str() { "borrowing" => EitherOf3::A(LoanerBorrowForm),
"borrowing" => EitherOf3::A(LoanerBorrowForm), "returning" => EitherOf3::B(LoanerReturnForm),
"returning" => EitherOf3::B(LoanerReturnForm), _ => EitherOf3::C("Choose something from the left.".into_view()),
_ => EitherOf3::C("Choose something from the left.".into_view())
}
}; };
// We'll create the page here - the sidebar contains all the buttons, while the form // We'll create the page here - the sidebar contains all the buttons, while the form
@@ -221,21 +250,21 @@ pub fn LoanerBorrowForm() -> impl IntoView {
let temp = ExpandableInfo::new(uuid::Uuid::new_v4().to_string()); let temp = ExpandableInfo::new(uuid::Uuid::new_v4().to_string());
let temp_reactive = temp.as_reactive(); let temp_reactive = temp.as_reactive();
let elements_disabled = RwSignal::new(false); let elements_disabled = RwSignal::new(false);
view! { view! {
<div class="flex flex-col justify-center gap-2 mb-1 py-3 px-2"> <div class="flex flex-col justify-center gap-2 mb-1 py-3 px-2">
<h2 class="text-2xl font-bold">"Borrower Information"</h2> <h2 class="text-2xl font-bold">"Borrower Information"</h2>
<p class="text-lg">"Please fill out the form below."</p> <p class="text-lg">"Please fill out the form below."</p>
</div> </div>
<Row> <Row>
<OutlinedTextField <OutlinedTextField
label="First Name:" label="First Name:"
placeholder="John" placeholder="John"
disabled=elements_disabled disabled=elements_disabled
data_member="first_name" data_member="first_name"
data_map=temp_reactive.data data_map=temp_reactive.data
/> />
<OutlinedTextField <OutlinedTextField
label="Last Name:" label="Last Name:"
placeholder="Smith" placeholder="Smith"
disabled=elements_disabled disabled=elements_disabled
@@ -244,7 +273,7 @@ pub fn LoanerBorrowForm() -> impl IntoView {
/> />
</Row> </Row>
<Row> <Row>
<OutlinedTextField <OutlinedTextField
label="Region 15 Email:" label="Region 15 Email:"
placeholder="example@region15.org" placeholder="example@region15.org"
disabled=elements_disabled disabled=elements_disabled
@@ -268,7 +297,7 @@ pub fn LoanerBorrowForm() -> impl IntoView {
/> />
</Row> </Row>
<Row> <Row>
<Select <Select
label="Loan Taken:" label="Loan Taken:"
value_list=vec![ value_list=vec![
"Chromebook".to_string(), "Chromebook".to_string(),
@@ -305,10 +334,12 @@ pub fn LoanerReturnForm() -> impl IntoView {
let return_list_refresh = RwSignal::new(0); let return_list_refresh = RwSignal::new(0);
let return_list_resource: Resource<Vec<LoanerReturnOutput>> = Resource::new( let return_list_resource: Resource<Vec<LoanerReturnOutput>> = Resource::new(
move || return_list_refresh.get(), move || return_list_refresh.get(),
async |_| get_loaners_return_list().await.unwrap_or_else(|e| { async |_| {
log!("There was an error getting the loaner list: {}", e); get_loaners_return_list().await.unwrap_or_else(|e| {
vec![] log!("There was an error getting the loaner list: {}", e);
}) vec![]
})
},
); );
let dialog_ref = NodeRef::<Dialog>::new(); let dialog_ref = NodeRef::<Dialog>::new();
@@ -340,7 +371,9 @@ pub fn LoanerReturnForm() -> impl IntoView {
let dialog_check_in = move |_| { let dialog_check_in = move |_| {
chosen_student.with(|opt_student| { chosen_student.with(|opt_student| {
if let Some(student) = opt_student.as_ref() { if let Some(student) = opt_student.as_ref() {
check_in_action.dispatch(CheckInLoaner { student: student.clone() }); check_in_action.dispatch(CheckInLoaner {
student: student.clone(),
});
} }
}) })
}; };
+5 -5
View File
@@ -1,15 +1,15 @@
mod about_page; mod about_page;
mod api;
mod comparison_test; mod comparison_test;
mod home_page; mod home_page;
mod loaner_page;
mod provider_portal; mod provider_portal;
mod scholarship_info;
mod test_page; mod test_page;
mod unauthenticated_page; mod unauthenticated_page;
mod scholarship_info;
mod utils; mod utils;
mod api;
mod loaner_page;
pub use self::{ pub use self::{
about_page::*, home_page::*, provider_portal::*, test_page::*, unauthenticated_page::*, about_page::*, comparison_test::*, home_page::*, loaner_page::LoanerPage, provider_portal::*,
loaner_page::LoanerPage, comparison_test::*, test_page::*, unauthenticated_page::*,
}; };
+41
View File
@@ -1 +1,42 @@
use crate::components::{Banner, DashboardButton};
use leptos::prelude::*;
#[component]
pub fn ProviderPortal() -> impl IntoView {
view! {
<Banner
title="Provider Dashboard"
logo="PHS_Stacked_Acronym.png"
path="/"
/>
<div class="mx-auto px-6">
<div class="container mx-auto px-6 py-8 space-y-8">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<section class="space-y-4">
<DashboardButton
title="Profile"
description="Edit user profile"
icon="/Person_Black.png"
path="/"
/>
<DashboardButton
title="Create Scholarship"
description="Navigate to Scholarship Creation Page"
icon="/Create_Black.png"
path="/"
/>
<DashboardButton
title="Applicants"
description="View Scholarship Applicants"
icon="/Form_Black.png"
path="/"
/>
</section>
<section class="space-y-4">
</section>
</div>
</div>
</div>
}
}
+30 -26
View File
@@ -1,21 +1,24 @@
use super::UnauthenticatedPage;
use super::api::{
CreateScholarshipInfo, CreateTestComparisons, get_comparison_info, get_scholarship_info,
};
use crate::common::ComparisonData;
use crate::common::{ExpandableInfo, ScholarshipFormParams};
use crate::components::{
ActionButton, ChipsList, Loading, OutlinedTextField, Panel, RadioList, Row,
};
use leptos::logging::log; use leptos::logging::log;
use leptos::prelude::*; use leptos::prelude::*;
use leptos_oidc::{AuthLoaded, Authenticated}; use leptos_oidc::{AuthLoaded, Authenticated};
use leptos_router::hooks::use_params; use leptos_router::hooks::use_params;
use crate::common::{ExpandableInfo, ScholarshipFormParams};
use crate::components::{ActionButton, ChipsList, Loading, OutlinedTextField, Panel, RadioList, Row};
use super::UnauthenticatedPage;
use traits::{AsReactive, ReactiveCapture}; use traits::{AsReactive, ReactiveCapture};
use crate::common::ComparisonData;
use super::api::{get_comparison_info, get_scholarship_info, CreateScholarshipInfo, CreateTestComparisons};
/// # Scholarship Info Page /// # Scholarship Info Page
/// This page will handle creating or editing scholarship information given a specific /// This page will handle creating or editing scholarship information given a specific
/// scholarship ID and scholarship provider subject number. The form itself will only /// scholarship ID and scholarship provider subject number. The form itself will only
/// track a single scholarship (for now) and so selection of what scholarship will be edited /// track a single scholarship (for now) and so selection of what scholarship will be edited
/// must be handled *before* navigation to this page. /// must be handled *before* navigation to this page.
/// ///
/// In its current testing form, we'll be using a single fixed ID. This absolutely *MUST* be /// In its current testing form, we'll be using a single fixed ID. This absolutely *MUST* be
/// changed before a full release. /// changed before a full release.
#[component] #[component]
@@ -23,17 +26,19 @@ pub fn ScholarshipInfoPage() -> impl IntoView {
// let scholarship_id = String::from("test-scholarship-id2"); // let scholarship_id = String::from("test-scholarship-id2");
let url_params = use_params::<ScholarshipFormParams>(); let url_params = use_params::<ScholarshipFormParams>();
let scholarship_id = move || { let scholarship_id = move || {
url_params.read() url_params
.read()
.as_ref() .as_ref()
.ok() .ok()
.and_then(|params| params.id.clone()) .and_then(|params| params.id.clone())
.unwrap_or_default() .unwrap_or_default()
}; };
let get_scholarship_action: Resource<ExpandableInfo> = Resource::new( let get_scholarship_action: Resource<ExpandableInfo> = Resource::new(
move || scholarship_id().clone(), move || scholarship_id().clone(),
async |id| { async |id| {
get_scholarship_info(id.clone()).await get_scholarship_info(id.clone())
.await
// Note that this really should notify us of an error and then redirect or ask // Note that this really should notify us of an error and then redirect or ask
// the user to try again. // the user to try again.
.unwrap_or_else(|e| { .unwrap_or_else(|e| {
@@ -41,23 +46,22 @@ pub fn ScholarshipInfoPage() -> impl IntoView {
log!("Using default ExpandableInfo"); log!("Using default ExpandableInfo");
ExpandableInfo::new(id) ExpandableInfo::new(id)
}) })
} },
); );
let get_comparison_info: Resource<Vec<ComparisonData>> = Resource::new( let get_comparison_info: Resource<Vec<ComparisonData>> = Resource::new(
move || Option::<String>::None, // There's no input to this function. move || Option::<String>::None, // There's no input to this function.
async |_| { async |_| {
get_comparison_info().await get_comparison_info().await.unwrap_or_else(|e| {
.unwrap_or_else(|e| { log!("Failed to get comparison info: {:?}", e);
log!("Failed to get comparison info: {:?}", e); Vec::new()
Vec::new() })
}) },
}
); );
let create_scholarship_action = ServerAction::<CreateScholarshipInfo>::new(); let create_scholarship_action = ServerAction::<CreateScholarshipInfo>::new();
let create_test_comps = ServerAction::<CreateTestComparisons>::new(); let create_test_comps = ServerAction::<CreateTestComparisons>::new();
// Remember that we only want to show the page after we've got authentication information // Remember that we only want to show the page after we've got authentication information
// and if there's data for us to use. // and if there's data for us to use.
view! { view! {
@@ -69,11 +73,11 @@ pub fn ScholarshipInfoPage() -> impl IntoView {
{move || Suspend::new(async move { {move || Suspend::new(async move {
let response = get_scholarship_action.get(); let response = get_scholarship_action.get();
let comparisons = get_comparison_info.get(); let comparisons = get_comparison_info.get();
response response
.zip(comparisons) .zip(comparisons)
.map(|(response, comparison_list)| view! { .map(|(response, comparison_list)| view! {
<ScholarshipForm <ScholarshipForm
response=response response=response
comparison_list=comparison_list comparison_list=comparison_list
submit_action=create_scholarship_action submit_action=create_scholarship_action
@@ -102,7 +106,7 @@ pub fn ScholarshipInfoPage() -> impl IntoView {
fn ScholarshipForm( fn ScholarshipForm(
response: ExpandableInfo, response: ExpandableInfo,
comparison_list: Vec<ComparisonData>, comparison_list: Vec<ComparisonData>,
submit_action: ServerAction<CreateScholarshipInfo> submit_action: ServerAction<CreateScholarshipInfo>,
) -> impl IntoView { ) -> impl IntoView {
let reactive_info = response.as_reactive(); let reactive_info = response.as_reactive();
let elements_disabled = RwSignal::new(false); let elements_disabled = RwSignal::new(false);
@@ -128,7 +132,7 @@ fn ScholarshipForm(
// We'll collect the scholarship's name, num_awards, amount_per_award, total_awards, // We'll collect the scholarship's name, num_awards, amount_per_award, total_awards,
// fafsa_required, award_to, transcript_required, recipient_selection, essay_requirement (and prompt) // fafsa_required, award_to, transcript_required, recipient_selection, essay_requirement (and prompt)
// award_night_remarks // award_night_remarks
view! { view! {
<Panel> <Panel>
<Row> <Row>
+2 -2
View File
@@ -1,5 +1,5 @@
use leptos::logging::log;
use crate::components::ActionButton; use crate::components::ActionButton;
use leptos::logging::log;
use leptos::prelude::*; use leptos::prelude::*;
use leptos_oidc::AuthSignal; use leptos_oidc::AuthSignal;
@@ -12,7 +12,7 @@ pub fn UnauthenticatedPage() -> impl IntoView {
.map(|unauthenticated| unauthenticated.login_url()) .map(|unauthenticated| unauthenticated.login_url())
}) })
}; };
Effect::new(move || log!("Generated login url: {:?}", login_url())); Effect::new(move || log!("Generated login url: {:?}", login_url()));
let navigate = leptos_router::hooks::use_navigate(); let navigate = leptos_router::hooks::use_navigate();
+2 -2
View File
@@ -1,5 +1,5 @@
/// # Server Utilities /// # Server Utilities
/// ///
/// These utility functions can only be used from the server. They are created to use server-side /// These utility functions can only be used from the server. They are created to use server-side
/// dependencies like the AWS SDK. Use of these functions will crash the front end, as these /// dependencies like the AWS SDK. Use of these functions will crash the front end, as these
/// dependencies cannot be compiled for WASM (nor should they be). /// dependencies cannot be compiled for WASM (nor should they be).
@@ -11,7 +11,7 @@ pub mod server_utils {
pub async fn create_aws_config() -> SdkConfig { pub async fn create_aws_config() -> SdkConfig {
aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await
} }
/// Creates a DynamoDB client. /// Creates a DynamoDB client.
pub async fn create_dynamo_client() -> aws_sdk_dynamodb::Client { pub async fn create_dynamo_client() -> aws_sdk_dynamodb::Client {
aws_sdk_dynamodb::Client::new(&create_aws_config().await) aws_sdk_dynamodb::Client::new(&create_aws_config().await)