feat: add edit, delete, and fallback capability to providers/scholarships
This commit is contained in:
1 parent
a0049d4797
commit
b687125d9b
8 files changed
+291
-222
No files matched your search
+2
-1
@@ -4,5 +4,6 @@ mod form_schema;
|
|||||||
mod user;
|
mod user;
|
||||||
mod value_type;
|
mod value_type;
|
||||||
mod form_params;
|
mod form_params;
|
||||||
|
mod submit_status;
|
||||||
|
|
||||||
pub use self::{comparison::*, expandable_info::*, form_schema::*, user::*, value_type::*, form_params::*};
|
pub use self::{comparison::*, expandable_info::*, form_schema::*, user::*, value_type::*, form_params::*, submit_status::*};
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum SubmitStatus {
|
||||||
|
Idle,
|
||||||
|
Sending,
|
||||||
|
Success,
|
||||||
|
Error(String),
|
||||||
|
}
|
||||||
@@ -2,8 +2,8 @@ use leptos::prelude::*;
|
|||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn ActionButton(
|
pub fn ActionButton(
|
||||||
#[prop(optional, default = String::from("button"))] button_type: String,
|
#[prop(default = String::from("button"))] button_type: String,
|
||||||
#[prop(optional, default = RwSignal::new(false))] disabled: RwSignal<bool>,
|
#[prop(optional, into)] disabled: Signal<bool>,
|
||||||
children: Children,
|
children: Children,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
view! {
|
view! {
|
||||||
|
|||||||
@@ -70,8 +70,8 @@ pub fn Chip(
|
|||||||
pub fn ChipsList(
|
pub fn ChipsList(
|
||||||
#[prop(into)] data_member: String,
|
#[prop(into)] data_member: String,
|
||||||
#[prop()] data_map: RwSignal<HashMap<String, ValueType>>,
|
#[prop()] data_map: RwSignal<HashMap<String, ValueType>>,
|
||||||
#[prop()] displayed_text: Vec<String>,
|
#[prop(into)] displayed_text: Signal<Vec<String>>,
|
||||||
#[prop()] values: Vec<String>,
|
#[prop(into)] values: Signal<Vec<String>>,
|
||||||
#[prop(default = RwSignal::new(false))] disabled: RwSignal<bool>,
|
#[prop(default = RwSignal::new(false))] disabled: RwSignal<bool>,
|
||||||
#[prop(optional, into)] label: String,
|
#[prop(optional, into)] label: String,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
@@ -79,7 +79,7 @@ pub fn ChipsList(
|
|||||||
<div class="m-1.5 mt-0 mb-0">
|
<div class="m-1.5 mt-0 mb-0">
|
||||||
<span class="font-bold">{label}</span>
|
<span class="font-bold">{label}</span>
|
||||||
<div class="flex flex-row flex-wrap gap-2 mt-1">
|
<div class="flex flex-row flex-wrap gap-2 mt-1">
|
||||||
{displayed_text.into_iter().zip(values)
|
{move || { displayed_text.get().into_iter().zip(values.get())
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(text, value)| {
|
.map(|(text, value)| {
|
||||||
let checked_signal = Signal::derive({
|
let checked_signal = Signal::derive({
|
||||||
@@ -140,7 +140,7 @@ pub fn ChipsList(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect_view()
|
.collect_view()
|
||||||
}
|
}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ pub fn Radio(
|
|||||||
#[prop(into)] on_change: Callback<(), ()>,
|
#[prop(into)] on_change: Callback<(), ()>,
|
||||||
#[prop(into)] value: String,
|
#[prop(into)] value: String,
|
||||||
#[prop(into)] name: String,
|
#[prop(into)] name: String,
|
||||||
#[prop(default = RwSignal::new(false))] disabled: RwSignal<bool>,
|
#[prop(optional, into)] disabled: Signal<bool>,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
let id = create_unique_id(&name, &value);
|
let id = create_unique_id(&name, &value);
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ pub fn RadioList(
|
|||||||
#[prop(into)] data_member: String,
|
#[prop(into)] data_member: String,
|
||||||
#[prop()] data_map: RwSignal<HashMap<String, ValueType>>,
|
#[prop()] data_map: RwSignal<HashMap<String, ValueType>>,
|
||||||
#[prop()] items: Vec<String>,
|
#[prop()] items: Vec<String>,
|
||||||
#[prop(default = RwSignal::new(false))] disabled: RwSignal<bool>,
|
#[prop(optional, into)] disabled: Signal<bool>,
|
||||||
#[prop(optional, into)] label: String,
|
#[prop(optional, into)] label: String,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
view! {
|
view! {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ pub fn OutlinedTextField(
|
|||||||
#[prop()] data_map: RwSignal<HashMap<String, ValueType>>,
|
#[prop()] data_map: RwSignal<HashMap<String, ValueType>>,
|
||||||
#[prop(optional)] value: RwSignal<ValueType>,
|
#[prop(optional)] value: RwSignal<ValueType>,
|
||||||
#[prop(default = "text".to_owned(), into)] input_type: String,
|
#[prop(default = "text".to_owned(), into)] input_type: String,
|
||||||
#[prop(optional)] disabled: RwSignal<bool>,
|
#[prop(into, optional)] disabled: Signal<bool>,
|
||||||
#[prop(optional)] error: RwSignal<bool>,
|
#[prop(optional)] error: RwSignal<bool>,
|
||||||
#[prop(optional, into)] name: String,
|
#[prop(optional, into)] name: String,
|
||||||
#[prop(optional, into)] label: String,
|
#[prop(optional, into)] label: String,
|
||||||
|
|||||||
@@ -164,3 +164,31 @@ pub async fn register_scholarship(provider_id: String, scholarship_name: String)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Deletes a provider's scholarship given their provider ID and scholarship ID.
|
||||||
|
#[server(DeleteProviderScholarship, "/providers/scholarships/delete")]
|
||||||
|
pub async fn delete_provider_scholarship(provider_id: String, scholarship_id: String) -> Result<(), ServerFnError> {
|
||||||
|
let client = create_dynamo_client().await;
|
||||||
|
|
||||||
|
log!("Deleting scholarship with ID {:?} for provider with ID {:?}", scholarship_id, provider_id);
|
||||||
|
|
||||||
|
// When we delete a scholarship, we need to ensure that the provider's ID matches the scholarship,
|
||||||
|
// otherwise everyone can delete anyone else's scholarships.
|
||||||
|
match client
|
||||||
|
.delete_item()
|
||||||
|
.table_name("leptos-scholarship-test")
|
||||||
|
.key("subject", AttributeValue::S(scholarship_id))
|
||||||
|
.expression_attribute_values(":provider_id", serde_dynamo::to_attribute_value(ValueType::String(Some(provider_id)))?)
|
||||||
|
.condition_expression("provider_id = :provider_id")
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
let msg = err.message().unwrap_or("An unknown error occurred");
|
||||||
|
Err(ServerFnError::new(msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+245
-212
@@ -1,15 +1,16 @@
|
|||||||
use leptos::either::Either;
|
use leptos::either::Either;
|
||||||
use leptos::logging::log;
|
use leptos::logging::log;
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
|
use leptos::reactive::spawn_local;
|
||||||
use leptos_oidc::{AuthLoaded, Authenticated};
|
use leptos_oidc::{AuthLoaded, Authenticated};
|
||||||
use leptos_router::hooks::{use_navigate, use_params};
|
use leptos_router::hooks::{use_navigate, use_params};
|
||||||
use crate::common::{ExpandableInfo, ScholarshipFormParams, ValueType};
|
use crate::common::{ExpandableInfo, ScholarshipFormParams, SubmitStatus, ValueType};
|
||||||
use crate::components::{ActionButton, Banner, ChipsList, Loading, OutlinedTextField, Panel, RadioList, Row};
|
use crate::components::{ActionButton, Banner, ChipsList, Loading, OutlinedTextField, Panel, RadioList, Row};
|
||||||
use super::UnauthenticatedPage;
|
use super::UnauthenticatedPage;
|
||||||
use traits::{AsReactive, ReactiveCapture};
|
use traits::{AsReactive, ReactiveCapture};
|
||||||
use crate::common::ComparisonData;
|
use crate::common::ComparisonData;
|
||||||
use crate::pages::utils::get_user_claims;
|
use crate::pages::utils::get_user_claims;
|
||||||
use super::api::{get_comparison_info, get_provider_scholarships, get_scholarship_info, CreateScholarshipInfo, CreateTestComparisons, RegisterScholarship};
|
use super::api::{get_comparison_info, get_provider_scholarships, get_scholarship_info, delete_provider_scholarship, CreateScholarshipInfo, CreateTestComparisons, RegisterScholarship};
|
||||||
|
|
||||||
|
|
||||||
/// # Scholarship Info Page
|
/// # Scholarship Info Page
|
||||||
@@ -20,40 +21,13 @@ use super::api::{get_comparison_info, get_provider_scholarships, get_scholarship
|
|||||||
#[component]
|
#[component]
|
||||||
pub fn ScholarshipInfoPage() -> impl IntoView {
|
pub fn ScholarshipInfoPage() -> impl IntoView {
|
||||||
let url_params = use_params::<ScholarshipFormParams>();
|
let url_params = use_params::<ScholarshipFormParams>();
|
||||||
let scholarship_id = move || {
|
let scholarship_id = Memo::new(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()
|
});
|
||||||
};
|
|
||||||
|
|
||||||
let get_scholarship_action: Resource<ExpandableInfo> = Resource::new(
|
|
||||||
move || scholarship_id().clone(),
|
|
||||||
async |id| {
|
|
||||||
get_scholarship_info(id.clone()).await
|
|
||||||
// Note that this really should notify us of an error and then redirect or ask
|
|
||||||
// the user to try again.
|
|
||||||
.unwrap_or_else(|e| {
|
|
||||||
log!("Failed to get scholarship info: {:?}", e);
|
|
||||||
log!("Using default ExpandableInfo");
|
|
||||||
ExpandableInfo::new(id)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
let get_comparison_info: Resource<Vec<ComparisonData>> = Resource::new(
|
|
||||||
move || Option::<String>::None, // There's no input to this function.
|
|
||||||
async |_| {
|
|
||||||
get_comparison_info().await
|
|
||||||
.unwrap_or_else(|e| {
|
|
||||||
log!("Failed to get comparison info: {:?}", e);
|
|
||||||
Vec::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
|
||||||
@@ -66,37 +40,25 @@ pub fn ScholarshipInfoPage() -> impl IntoView {
|
|||||||
/>
|
/>
|
||||||
<AuthLoaded fallback=Loading>
|
<AuthLoaded fallback=Loading>
|
||||||
<Authenticated unauthenticated=UnauthenticatedPage>
|
<Authenticated unauthenticated=UnauthenticatedPage>
|
||||||
<Suspense fallback=Loading>
|
<div class="flex flex-row align-top items-start">
|
||||||
<div class="flex flex-row align-top items-start">
|
<div class="flex flex-col flex-1" />
|
||||||
<div class="flex flex-col flex-1" />
|
<ScholarshipList />
|
||||||
<ScholarshipList />
|
<Transition fallback=Loading>
|
||||||
{move || Suspend::new(async move {
|
<ScholarshipForm
|
||||||
let response = get_scholarship_action.get();
|
scholarship_id=scholarship_id
|
||||||
let comparisons = get_comparison_info.get();
|
/>
|
||||||
|
</Transition>
|
||||||
response
|
<div class="hidden">
|
||||||
.zip(comparisons)
|
<Row>
|
||||||
.map(|(response, comparison_list)| view! {
|
<ActionButton
|
||||||
<ScholarshipForm
|
on:click=move |_| {
|
||||||
response=response
|
create_test_comps.dispatch(CreateTestComparisons {});
|
||||||
comparison_list=comparison_list
|
}
|
||||||
submit_action=create_scholarship_action
|
>"Create test comparisons"</ActionButton>
|
||||||
/>
|
</Row>
|
||||||
})
|
|
||||||
.collect_view()
|
|
||||||
})}
|
|
||||||
<div class="hidden">
|
|
||||||
<Row>
|
|
||||||
<ActionButton
|
|
||||||
on:click=move |_| {
|
|
||||||
create_test_comps.dispatch(CreateTestComparisons {});
|
|
||||||
}
|
|
||||||
>"Create test comparisons"</ActionButton>
|
|
||||||
</Row>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col flex-1" />
|
|
||||||
</div>
|
</div>
|
||||||
</Suspense>
|
<div class="flex flex-col flex-1" />
|
||||||
|
</div>
|
||||||
</Authenticated>
|
</Authenticated>
|
||||||
</AuthLoaded>
|
</AuthLoaded>
|
||||||
}
|
}
|
||||||
@@ -115,9 +77,9 @@ fn ScholarshipList() -> impl IntoView {
|
|||||||
|
|
||||||
// Register scholarship server resource
|
// Register scholarship server resource
|
||||||
let scholarships = Resource::new(
|
let scholarships = Resource::new(
|
||||||
move || provider_id,
|
move || provider_id.get(),
|
||||||
async move |provider_id| {
|
async move |provider_id| {
|
||||||
let Some(provider_id) = provider_id.get() else {
|
let Some(provider_id) = provider_id else {
|
||||||
return Ok(Vec::new());
|
return Ok(Vec::new());
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -126,7 +88,7 @@ fn ScholarshipList() -> impl IntoView {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Register scholarship creation action
|
// Register scholarship creation/deletion actions
|
||||||
let create_action = ServerAction::<RegisterScholarship>::new();
|
let create_action = ServerAction::<RegisterScholarship>::new();
|
||||||
|
|
||||||
// Button handlers
|
// Button handlers
|
||||||
@@ -164,7 +126,12 @@ fn ScholarshipList() -> impl IntoView {
|
|||||||
match result {
|
match result {
|
||||||
Ok(list) => {
|
Ok(list) => {
|
||||||
let views = list.iter().map(|entry| {
|
let views = list.iter().map(|entry| {
|
||||||
view! { <ScholarshipListEntry scholarship=entry.clone() /> }
|
view! {
|
||||||
|
<ScholarshipListEntry scholarship=entry.clone()
|
||||||
|
on_delete=move || {
|
||||||
|
scholarships.refetch();
|
||||||
|
}/>
|
||||||
|
}
|
||||||
});
|
});
|
||||||
Either::Left(views.collect_view())
|
Either::Left(views.collect_view())
|
||||||
}
|
}
|
||||||
@@ -180,7 +147,7 @@ fn ScholarshipList() -> impl IntoView {
|
|||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="p-2 bg-red-700 rounded-md text-center text-white hover:bg-red-800 transition-all"
|
<div class="p-2 bg-red-800 rounded-md text-center text-white hover:bg-red-900 transition-all"
|
||||||
on:click=create_on_click>
|
on:click=create_on_click>
|
||||||
"Create New"
|
"Create New"
|
||||||
</div>
|
</div>
|
||||||
@@ -192,12 +159,15 @@ fn ScholarshipList() -> impl IntoView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
fn ScholarshipListEntry(scholarship: ExpandableInfo) -> impl IntoView {
|
fn ScholarshipListEntry(
|
||||||
|
#[prop()] scholarship: ExpandableInfo,
|
||||||
|
#[prop(into)] on_delete: Callback<()>,
|
||||||
|
) -> impl IntoView {
|
||||||
let navigate = use_navigate();
|
let navigate = use_navigate();
|
||||||
|
|
||||||
// Get the values that we need out of the info. We just need the subject and the name of
|
// Get the values that we need out of the info. We just need the subject and the name of
|
||||||
// the scholarship.
|
// the scholarship.
|
||||||
let name = scholarship.data.get("scholarship_name")
|
let name = scholarship.data.get("name")
|
||||||
.unwrap_or(&ValueType::String(None))
|
.unwrap_or(&ValueType::String(None))
|
||||||
.as_string().unwrap_or_default()
|
.as_string().unwrap_or_default()
|
||||||
.unwrap_or("<no name found>".to_string());
|
.unwrap_or("<no name found>".to_string());
|
||||||
@@ -214,8 +184,18 @@ fn ScholarshipListEntry(scholarship: ExpandableInfo) -> impl IntoView {
|
|||||||
let delete_click = {
|
let delete_click = {
|
||||||
let subject = scholarship.subject.clone();
|
let subject = scholarship.subject.clone();
|
||||||
move |_| {
|
move |_| {
|
||||||
// Delete the scholarship. For now, we'll just log it.
|
// Delete the scholarship.
|
||||||
|
let subject = subject.clone();
|
||||||
log!("Deleting scholarship with ID {:?}", subject);
|
log!("Deleting scholarship with ID {:?}", subject);
|
||||||
|
spawn_local(async move {
|
||||||
|
match delete_provider_scholarship(
|
||||||
|
subject,
|
||||||
|
"".to_string()
|
||||||
|
).await {
|
||||||
|
Ok(_) => on_delete.run(()),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -225,14 +205,14 @@ fn ScholarshipListEntry(scholarship: ExpandableInfo) -> impl IntoView {
|
|||||||
<span>{name}</span>
|
<span>{name}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row">
|
<div class="flex flex-row">
|
||||||
<div class="p-2 flex-1 bg-red-700 border-r border-white rounded-bl-md text-white text-center
|
<div class="p-2 flex-1 bg-red-800 border-r border-white rounded-bl-md text-white text-center
|
||||||
hover:bg-red-800 cursor-pointer transition-all"
|
hover:bg-red-900 cursor-pointer transition-all"
|
||||||
on:click=edit_click
|
on:click=edit_click
|
||||||
>
|
>
|
||||||
"Edit"
|
"Edit"
|
||||||
</div>
|
</div>
|
||||||
<div class="p-2 flex-1 bg-red-700 border-l border-white rounded-br-md text-white text-center
|
<div class="p-2 flex-1 bg-red-800 border-l border-white rounded-br-md text-white text-center
|
||||||
hover:bg-red-800 cursor-pointer transition-all"
|
hover:bg-red-900 cursor-pointer transition-all"
|
||||||
on:click=delete_click
|
on:click=delete_click
|
||||||
>
|
>
|
||||||
"Delete"
|
"Delete"
|
||||||
@@ -244,30 +224,71 @@ fn ScholarshipListEntry(scholarship: ExpandableInfo) -> impl IntoView {
|
|||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
fn ScholarshipForm(
|
fn ScholarshipForm(
|
||||||
response: ExpandableInfo,
|
#[prop(into)] scholarship_id: Signal<Option<String>>
|
||||||
comparison_list: Vec<ComparisonData>,
|
|
||||||
submit_action: ServerAction<CreateScholarshipInfo>
|
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
let reactive_info = response.as_reactive();
|
let submit_status = RwSignal::new(SubmitStatus::Idle);
|
||||||
let elements_disabled = RwSignal::new(false);
|
let elements_disabled = Signal::derive(move || {
|
||||||
|
match submit_status.get() {
|
||||||
|
SubmitStatus::Sending => true,
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
});
|
||||||
let result_msg = Signal::derive(move || {
|
let result_msg = Signal::derive(move || {
|
||||||
|
match submit_status.get() {
|
||||||
|
SubmitStatus::Idle => "".into(),
|
||||||
|
SubmitStatus::Sending => "Sending submission...".into(),
|
||||||
|
SubmitStatus::Success => "Success".into(),
|
||||||
|
SubmitStatus::Error(msg) => format!("Error: {}", msg),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let submit_action = ServerAction::<CreateScholarshipInfo>::new();
|
||||||
|
|
||||||
|
Effect::new(move || {
|
||||||
if submit_action.pending().get() {
|
if submit_action.pending().get() {
|
||||||
elements_disabled.set(true);
|
submit_status.set(SubmitStatus::Sending);
|
||||||
"Sending request...".to_string()
|
return;
|
||||||
} else {
|
}
|
||||||
elements_disabled.set(false);
|
|
||||||
if let Some(result) = submit_action.value().get() {
|
if let Some(result) = submit_action.value().get() {
|
||||||
match result {
|
match result {
|
||||||
Ok(()) => "Request sent successfully".to_string(),
|
Ok(()) => submit_status.set(SubmitStatus::Success),
|
||||||
Err(err) => {
|
Err(err) => submit_status.set(SubmitStatus::Error(err.to_string()))
|
||||||
format!("An error occurred: {:?}", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
"".to_string()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let comparison_list: Resource<Vec<ComparisonData>> = Resource::new(
|
||||||
|
move || (), // There's no input to this function.
|
||||||
|
async move |_| {
|
||||||
|
get_comparison_info().await
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
log!("Failed to get comparison info: {:?}", e);
|
||||||
|
Vec::new()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let scholarship_info: Resource<Result<ExpandableInfo, ServerFnError>> = Resource::new(
|
||||||
|
move || scholarship_id.get().unwrap_or_default(),
|
||||||
|
async move |id| {
|
||||||
|
get_scholarship_info(id).await
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let comparison_ids = Memo::new(move |_| {
|
||||||
|
comparison_list.get().map(|list| list.iter()
|
||||||
|
.map(|comp| comp.clone().id)
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
).unwrap_or_default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let comparison_text = Memo::new(move |_| {
|
||||||
|
comparison_list.get().map(|list| list.iter()
|
||||||
|
.map(|comp| comp.clone().display_text)
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
).unwrap_or_default()
|
||||||
|
});
|
||||||
|
|
||||||
// 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)
|
||||||
@@ -275,127 +296,139 @@ fn ScholarshipForm(
|
|||||||
|
|
||||||
view! {
|
view! {
|
||||||
<Panel>
|
<Panel>
|
||||||
<Row>
|
<Suspense fallback=Loading>
|
||||||
<h1 class="text-3xl font-bold">
|
{move || {
|
||||||
"Scholarship Info Form (test)"
|
scholarship_info.get()
|
||||||
</h1>
|
.map(|res_scholarship| {
|
||||||
</Row>
|
match res_scholarship {
|
||||||
<Row>
|
Ok(scholarship) => {
|
||||||
<OutlinedTextField
|
let reactive_info = scholarship.as_reactive();
|
||||||
label="Scholarship Name"
|
|
||||||
placeholder="Example Scholarship Name"
|
Either::Left(view! {
|
||||||
disabled=elements_disabled
|
<Row>
|
||||||
data_member="name"
|
<h1 class="text-3xl font-bold">
|
||||||
data_map=reactive_info.data
|
"Scholarship Info Form (test)"
|
||||||
/>
|
</h1>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
<OutlinedTextField
|
<OutlinedTextField
|
||||||
label="Amount per award: (if multiple, enter the highest value)"
|
label="Scholarship Name"
|
||||||
placeholder="Enter an amount..."
|
placeholder="Example Scholarship Name"
|
||||||
disabled=elements_disabled
|
disabled=elements_disabled
|
||||||
data_member="amount_per_award"
|
data_member="name"
|
||||||
data_map=reactive_info.data
|
data_map=reactive_info.data
|
||||||
input_type="number"
|
/>
|
||||||
/>
|
</Row>
|
||||||
</Row>
|
<Row>
|
||||||
<Row>
|
<OutlinedTextField
|
||||||
<OutlinedTextField
|
label="Amount per award: (if multiple, enter the highest value)"
|
||||||
label="Total number of awards:"
|
placeholder="Enter an amount..."
|
||||||
placeholder="Enter an amount..."
|
disabled=elements_disabled
|
||||||
disabled=elements_disabled
|
data_member="amount_per_award"
|
||||||
data_member="num_awards"
|
data_map=reactive_info.data
|
||||||
data_map=reactive_info.data
|
input_type="number"
|
||||||
input_type="number"
|
/>
|
||||||
/>
|
</Row>
|
||||||
</Row>
|
<Row>
|
||||||
<Row>
|
<OutlinedTextField
|
||||||
<OutlinedTextField
|
label="Total number of awards:"
|
||||||
label="Total amount of all awards:"
|
placeholder="Enter an amount..."
|
||||||
placeholder="Enter an amount..."
|
disabled=elements_disabled
|
||||||
disabled=elements_disabled
|
data_member="num_awards"
|
||||||
data_member="total_awards"
|
data_map=reactive_info.data
|
||||||
data_map=reactive_info.data
|
input_type="number"
|
||||||
input_type="number"
|
/>
|
||||||
/>
|
</Row>
|
||||||
</Row>
|
<Row>
|
||||||
<Row>
|
<OutlinedTextField
|
||||||
<RadioList
|
label="Total amount of all awards:"
|
||||||
data_member="fafsa_required"
|
placeholder="Enter an amount..."
|
||||||
data_map=reactive_info.data
|
disabled=elements_disabled
|
||||||
items=vec!["Yes".to_string(), "No".to_string()]
|
data_member="total_awards"
|
||||||
disabled=elements_disabled
|
data_map=reactive_info.data
|
||||||
label="Do you require student financial information?"
|
input_type="number"
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
<RadioList
|
<RadioList
|
||||||
data_member="transcript_required"
|
data_member="fafsa_required"
|
||||||
data_map=reactive_info.data
|
data_map=reactive_info.data
|
||||||
items=vec!["Yes".to_string(), "No".to_string()]
|
items=vec!["Yes".to_string(), "No".to_string()]
|
||||||
disabled=elements_disabled
|
disabled=elements_disabled
|
||||||
label="Do you require a student transcript?"
|
label="Do you require student financial information?"
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
<RadioList
|
<RadioList
|
||||||
data_member="award_to"
|
data_member="transcript_required"
|
||||||
data_map=reactive_info.data
|
data_map=reactive_info.data
|
||||||
items=vec!["School".to_string(), "Student".to_string()]
|
items=vec!["Yes".to_string(), "No".to_string()]
|
||||||
disabled=elements_disabled
|
disabled=elements_disabled
|
||||||
label="Will the award be made to the school or the student?"
|
label="Do you require a student transcript?"
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
<RadioList
|
<RadioList
|
||||||
data_member="essay_required"
|
data_member="award_to"
|
||||||
data_map=reactive_info.data
|
data_map=reactive_info.data
|
||||||
items=vec!["Yes".to_string(), "No".to_string()]
|
items=vec!["School".to_string(), "Student".to_string()]
|
||||||
disabled=elements_disabled
|
disabled=elements_disabled
|
||||||
label="Do you require a student essay?"
|
label="Will the award be made to the school or the student?"
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
<RadioList
|
<RadioList
|
||||||
data_member="essay_prompt"
|
data_member="essay_required"
|
||||||
data_map=reactive_info.data
|
data_map=reactive_info.data
|
||||||
items=vec!["Test 1", "Test 2", "Test 3"].iter().map(|s| s.to_string()).collect()
|
items=vec!["Yes".to_string(), "No".to_string()]
|
||||||
disabled=elements_disabled
|
disabled=elements_disabled
|
||||||
label="If so, select a prompt from the list below."
|
label="Do you require a student essay?"
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
<ChipsList
|
<RadioList
|
||||||
label="Scholarship Requirements"
|
data_member="essay_prompt"
|
||||||
data_member="requirements"
|
data_map=reactive_info.data
|
||||||
data_map=reactive_info.data
|
items=vec!["Test 1", "Test 2", "Test 3"].iter().map(|s| s.to_string()).collect()
|
||||||
values=comparison_list
|
disabled=elements_disabled
|
||||||
.iter()
|
label="If so, select a prompt from the list below."
|
||||||
.cloned()
|
/>
|
||||||
.map(|comp| comp.id)
|
</Row>
|
||||||
.collect()
|
<Row>
|
||||||
displayed_text=comparison_list
|
<ChipsList
|
||||||
.iter()
|
label="Scholarship Requirements"
|
||||||
.cloned()
|
data_member="requirements"
|
||||||
.map(|comp| comp.display_text)
|
data_map=reactive_info.data
|
||||||
.collect()
|
values=comparison_ids
|
||||||
/>
|
displayed_text=comparison_text
|
||||||
</Row>
|
/>
|
||||||
<Row>
|
</Row>
|
||||||
<ActionButton
|
<Row>
|
||||||
disabled=elements_disabled
|
<ActionButton
|
||||||
on:click=move |_| {
|
disabled=elements_disabled
|
||||||
let captured = reactive_info.capture();
|
on:click=move |_| {
|
||||||
log!("Map values: {:?}", captured);
|
let captured = reactive_info.capture();
|
||||||
submit_action.dispatch(CreateScholarshipInfo {
|
log!("Map values: {:?}", captured);
|
||||||
info: captured
|
submit_action.dispatch(CreateScholarshipInfo {
|
||||||
});
|
info: captured
|
||||||
}
|
});
|
||||||
>"Submit"</ActionButton>
|
}
|
||||||
</Row>
|
>"Submit"</ActionButton>
|
||||||
<Row>
|
</Row>
|
||||||
<p>{result_msg}</p>
|
<Row>
|
||||||
</Row>
|
<p>{result_msg}</p>
|
||||||
|
</Row>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
Either::Right(view! {
|
||||||
|
<p>"Error while getting scholarship: "{err.to_string()}</p>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).collect_view()
|
||||||
|
}}
|
||||||
|
</Suspense>
|
||||||
</Panel>
|
</Panel>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user