feat: reorganize and add single selection to chip lists
This commit is contained in:
1 parent
105e6b3e55
commit
8d9faf126d
6 files changed
+214
-61
No files matched your search
@@ -8,8 +8,8 @@ pub fn ActionButton(
|
||||
) -> impl IntoView {
|
||||
view! {
|
||||
<button
|
||||
class="m-1.5 p-2.5 rounded-lg font-semibold text-white transition-all duration-400"
|
||||
class=(["bg-red-700", "hover:bg-red-800"], move || !disabled.get())
|
||||
class="m-1.5 p-2.5 rounded-lg font-semibold text-white transition-all duration-400 cursor-pointer"
|
||||
class=(["bg-red-800", "hover:bg-red-900"], move || !disabled.get())
|
||||
class=(["bg-gray-600", "pointer-events-none"], move || disabled.get())
|
||||
r#type=button_type
|
||||
disabled=move || disabled.get()
|
||||
|
||||
@@ -70,13 +70,22 @@ pub fn Chip(
|
||||
/// passed to the `on_change` prop for each individual `Chip` component.
|
||||
#[component]
|
||||
pub fn ChipsList(
|
||||
/// The member in the data map that this input affects.
|
||||
#[prop(into)] data_member: String,
|
||||
/// The data map that contains the list of values. This will be edited as the input changes.
|
||||
#[prop()] data_map: RwSignal<HashMap<String, ValueType>>,
|
||||
/// The list of strings that should be displayed in the list.
|
||||
#[prop(into)] displayed_text: Signal<Vec<String>>,
|
||||
/// The list of values that should be displayed in the list.
|
||||
#[prop(into)] values: Signal<Vec<String>>,
|
||||
/// Indicates if the input should be disabled.
|
||||
#[prop(optional, into)] disabled: Signal<bool>,
|
||||
/// The label for the header of the input.
|
||||
#[prop(optional, into)] label: String,
|
||||
#[prop(optional, into)] required: Signal<bool>
|
||||
/// Indicates if this input is required.
|
||||
#[prop(optional, into)] required: Signal<bool>,
|
||||
/// Determines if this list behaves like a checkbox or radio list.
|
||||
#[prop(default = true.into(), into)] allows_multiple: Signal<bool>,
|
||||
) -> impl IntoView {
|
||||
let controller = use_selectable_list(
|
||||
data_member.clone(),
|
||||
@@ -88,41 +97,49 @@ pub fn ChipsList(
|
||||
<div class="flex flex-col">
|
||||
<div class="m-1.5 mt-0 mb-0">
|
||||
<span class="font-bold">{label}</span>
|
||||
{displayed_text.get().into_iter().zip(values.get())
|
||||
.into_iter()
|
||||
.map(|(text, value)| {
|
||||
let value = RwSignal::new(value);
|
||||
<div class="flex flex-row flex-wrap gap-2 mb-1">
|
||||
{move || displayed_text.get().into_iter().zip(values.get())
|
||||
.into_iter()
|
||||
.map(|(text, value)| {
|
||||
let value = RwSignal::new(value);
|
||||
|
||||
let checked = Signal::derive(move || {
|
||||
controller.selected_list.get().contains(&value.get())
|
||||
});
|
||||
|
||||
let on_change = move || {
|
||||
// Update the data list by checking if the value is present in it. If so,
|
||||
// remove it. If not, add it.
|
||||
controller.selected_list.update(|list| {
|
||||
let value = value.get();
|
||||
if list.contains(&value) {
|
||||
list.retain(|val| *val != value);
|
||||
} else {
|
||||
list.push(value.clone());
|
||||
}
|
||||
let checked = Signal::derive(move || {
|
||||
controller.selected_list.get().contains(&value.get())
|
||||
});
|
||||
controller.dirty.set(true);
|
||||
};
|
||||
|
||||
view! {
|
||||
<Chip
|
||||
checked=checked
|
||||
on_change=on_change
|
||||
value=value
|
||||
display_text=text
|
||||
name=data_member.clone()
|
||||
disabled=disabled
|
||||
/>
|
||||
}
|
||||
})
|
||||
.collect_view()}
|
||||
let on_change = move || {
|
||||
// Update the data list by checking if the value is present in it. If so,
|
||||
// remove it. If not, add it.
|
||||
let value = value.get();
|
||||
let list = controller.selected_list.get();
|
||||
|
||||
if list.contains(&value) {
|
||||
allows_multiple.get()
|
||||
.then(|| controller.selected_list.update(|list| list.retain(|v| v != &value)))
|
||||
.or_else(|| Some(controller.selected_list.set(Vec::new())));
|
||||
} else {
|
||||
allows_multiple.get()
|
||||
.then(|| controller.selected_list.update(|list| list.push(value.clone())))
|
||||
.or_else(|| Some(controller.selected_list.set(vec![value.clone()])));
|
||||
}
|
||||
|
||||
controller.dirty.set(true);
|
||||
};
|
||||
|
||||
view! {
|
||||
<Chip
|
||||
checked=checked
|
||||
on_change=on_change
|
||||
value=value
|
||||
display_text=text
|
||||
name=data_member.clone()
|
||||
disabled=disabled
|
||||
/>
|
||||
}
|
||||
})
|
||||
.collect_view()
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<Show when=move || controller.show_errors.get()>
|
||||
<div class="text-red-600 text-sm mr-1.5 ml-1.5">
|
||||
|
||||
@@ -98,7 +98,7 @@ pub fn Select(
|
||||
value=""
|
||||
disabled
|
||||
hidden
|
||||
selected=raw_value.get().is_empty()
|
||||
selected={move || raw_value.get().is_empty()}
|
||||
>"Select one..."</option>
|
||||
{move || {
|
||||
value_list.iter().map(
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
use std::collections::HashMap;
|
||||
#[cfg(feature = "ssr")]
|
||||
use crate::pages::utils::server_utils::create_dynamo_client;
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
use aws_sdk_dynamodb::error::ProvideErrorMetadata;
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
use leptos::logging::log;
|
||||
|
||||
use crate::common::{
|
||||
ComparisonData, ComparisonType, MapListComparison, NumberComparison, NumberListComparison,
|
||||
TextComparison, TextListComparison, ValueType,
|
||||
@@ -314,8 +318,6 @@ fn make_comp_list() -> Vec<ComparisonData> {
|
||||
|
||||
#[server(CreateTestComparisons, endpoint = "/comparisons/create-test")]
|
||||
pub async fn create_test_comparisons() -> Result<(), ServerFnError> {
|
||||
use leptos::logging::log;
|
||||
|
||||
let client = create_dynamo_client().await;
|
||||
|
||||
log!("Creating test comparisons");
|
||||
@@ -340,8 +342,6 @@ pub async fn create_test_comparisons() -> Result<(), ServerFnError> {
|
||||
|
||||
#[server]
|
||||
pub async fn get_comparison_info() -> Result<Vec<ComparisonData>, ServerFnError> {
|
||||
use leptos::logging::log;
|
||||
|
||||
let client = create_dynamo_client().await;
|
||||
|
||||
log!("Getting all comparisons from the database");
|
||||
@@ -368,3 +368,43 @@ pub async fn get_comparison_info() -> Result<Vec<ComparisonData>, ServerFnError>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn get_comparisons_categorized() -> Result<HashMap<String, Vec<ComparisonData>>, ServerFnError> {
|
||||
let client = create_dynamo_client().await;
|
||||
log!("Getting all comparisons from the database, by category.");
|
||||
|
||||
match client
|
||||
.scan()
|
||||
.table_name("leptos-comparison-test")
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(output) => {
|
||||
if let Some(items) = output.items {
|
||||
log!("Found comparisons, categorizing.");
|
||||
|
||||
let items: Vec<ComparisonData> = serde_dynamo::from_items(items)?;
|
||||
|
||||
let categorized = items.iter().fold(
|
||||
HashMap::<String, Vec<ComparisonData>>::new(), |mut map, comp| {
|
||||
// Load each comparison into a map, then return that map.
|
||||
map.entry(comp.category.clone())
|
||||
.and_modify(|vec| vec.push(comp.clone()))
|
||||
.or_insert(vec![comp.clone()]);
|
||||
|
||||
map
|
||||
}
|
||||
);
|
||||
|
||||
Ok(categorized)
|
||||
} else {
|
||||
Err(ServerFnError::new("Couldn't find any comparisons."))
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
let msg = err.message().unwrap_or("An unknown error occurred");
|
||||
Err(ServerFnError::new(msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
-1
@@ -8,7 +8,7 @@ use std::process::{Command, Stdio};
|
||||
#[cfg(feature = "ssr")]
|
||||
use std::io::Write;
|
||||
|
||||
use crate::common::ExpandableInfo;
|
||||
use crate::common::{ComparisonData, ExpandableInfo};
|
||||
use crate::pages::UnauthenticatedPage;
|
||||
use crate::pages::utils::get_user_claims;
|
||||
use crate::components::{ActionButton, CheckboxList, Loading, MultiEntry, OutlinedTextField, Panel, RadioList, Row, Select, TextFieldType, ValidatedForm};
|
||||
@@ -22,6 +22,7 @@ use leptos::task::spawn_local;
|
||||
use leptos::web_sys::HtmlAnchorElement;
|
||||
use leptos::wasm_bindgen::JsCast;
|
||||
use std::collections::HashMap;
|
||||
use crate::pages::api::get_comparisons_categorized;
|
||||
|
||||
/// # Get Student Info
|
||||
/// Gets a student's information given their `subject`.
|
||||
@@ -209,6 +210,28 @@ pub fn HomePage() -> impl IntoView {
|
||||
};
|
||||
//#endregion
|
||||
|
||||
let comparison_resource = Resource::new(
|
||||
move || user_subject.get(),
|
||||
async move |_| get_comparisons_categorized().await
|
||||
);
|
||||
|
||||
let comparison_info = RwSignal::new(HashMap::<String, Vec<ComparisonData>>::new());
|
||||
|
||||
Effect::new(move || {
|
||||
match comparison_resource.get() {
|
||||
Some(Ok(comparisons)) => {
|
||||
log!("Found comparison info: {:?}", comparisons);
|
||||
comparison_info.set(comparisons);
|
||||
}
|
||||
Some(Err(err)) => {
|
||||
log!("Error while getting comparisons: {:?}", err);
|
||||
}
|
||||
_ => {
|
||||
log!("An unknown error occurred.");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
view! {
|
||||
<AuthLoaded fallback=Loading>
|
||||
<Authenticated unauthenticated=UnauthenticatedPage>
|
||||
|
||||
@@ -8,9 +8,8 @@ use leptos_router::hooks::{use_navigate, use_params};
|
||||
use crate::common::{ExpandableInfo, ScholarshipFormParams, SubmitStatus, ValueType};
|
||||
use crate::components::{ActionButton, Banner, ChipsList, Loading, OutlinedTextField, Panel, RadioList, Row, TextFieldType, ValidatedForm};
|
||||
use super::UnauthenticatedPage;
|
||||
use crate::common::ComparisonData;
|
||||
use crate::pages::utils::get_user_claims;
|
||||
use super::api::{get_comparison_info, get_provider_scholarships, get_scholarship_info, CreateScholarshipInfo, RegisterScholarship, DeleteProviderScholarship};
|
||||
use super::api::{get_comparison_info, get_provider_scholarships, get_scholarship_info, CreateScholarshipInfo, RegisterScholarship, DeleteProviderScholarship, get_comparisons_categorized};
|
||||
|
||||
|
||||
/// # Scholarship Info Page
|
||||
@@ -245,7 +244,7 @@ fn ScholarshipList(
|
||||
}}
|
||||
</div>
|
||||
<div>
|
||||
<div class="p-2 bg-red-800 rounded-md text-center text-white hover:bg-red-900 transition-all"
|
||||
<div class="p-2 bg-red-800 rounded-md text-center text-white hover:bg-red-900 transition-all cursor-pointer"
|
||||
on:click=create_on_click>
|
||||
"Create New"
|
||||
</div>
|
||||
@@ -383,7 +382,7 @@ fn ScholarshipForm(
|
||||
}
|
||||
});
|
||||
|
||||
let comparison_list: Resource<Vec<ComparisonData>> = Resource::new(
|
||||
let comparison_list = Resource::new(
|
||||
move || scholarship_id.get(), // There's no input to this function.
|
||||
async move |_| {
|
||||
get_comparison_info().await
|
||||
@@ -394,6 +393,19 @@ fn ScholarshipForm(
|
||||
}
|
||||
);
|
||||
|
||||
let comparison_lists = Resource::new(
|
||||
move || scholarship_id.get().is_some(),
|
||||
async move |_| {
|
||||
get_comparisons_categorized().await
|
||||
}
|
||||
);
|
||||
|
||||
Effect::new(move || {
|
||||
if let Some(Ok(map)) = comparison_lists.get() {
|
||||
log!("Available categories: {:?}", map.keys());
|
||||
}
|
||||
});
|
||||
|
||||
let scholarship_info = Resource::new(
|
||||
move || scholarship_id.get(),
|
||||
async move |opt_id| {
|
||||
@@ -406,25 +418,67 @@ fn ScholarshipForm(
|
||||
);
|
||||
|
||||
let form_data = RwSignal::new(HashMap::new());
|
||||
let chips_data = RwSignal::new(HashMap::new());
|
||||
|
||||
Effect::new(move || {
|
||||
if let Some(Ok(scholarship)) = scholarship_info.get() {
|
||||
// Get the default chips data.
|
||||
let chips_default = if let Some(ValueType::Map(Some(map))) = scholarship.data.get("requirements") {
|
||||
map.clone()
|
||||
} else {
|
||||
HashMap::new()
|
||||
};
|
||||
|
||||
chips_data.set(chips_default);
|
||||
form_data.set(scholarship.data);
|
||||
}
|
||||
});
|
||||
|
||||
let comparison_ids = Memo::new(move |_| {
|
||||
comparison_list.get().map(|list| list.iter()
|
||||
.map(|comp| comp.clone().id)
|
||||
|
||||
let get_comp_category = move |category: &str| {
|
||||
if let Some(Ok(map)) = comparison_lists.get() {
|
||||
map.get(category).unwrap_or(&Vec::new()).clone()
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
|
||||
let get_comp_ids = move |category: &str| {
|
||||
get_comp_category(category)
|
||||
.into_iter()
|
||||
.map(|comp| comp.id)
|
||||
.collect::<Vec<String>>()
|
||||
).unwrap_or_default()
|
||||
};
|
||||
|
||||
let get_comp_text = move |category: &str| {
|
||||
get_comp_category(category)
|
||||
.into_iter()
|
||||
.map(|comp| comp.display_text)
|
||||
.collect::<Vec<String>>()
|
||||
};
|
||||
|
||||
// Get comparison lists.
|
||||
let sports_ids = Signal::derive(move || {
|
||||
get_comp_ids("Sports Participation")
|
||||
});
|
||||
|
||||
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()
|
||||
|
||||
let sports_text = Signal::derive(move || {
|
||||
get_comp_text("Sports Participation")
|
||||
});
|
||||
|
||||
let misc_ids = Signal::derive(move || {
|
||||
get_comp_ids("Miscellaneous")
|
||||
});
|
||||
|
||||
let misc_text = Signal::derive(move || {
|
||||
get_comp_text("Miscellaneous")
|
||||
});
|
||||
|
||||
let comm_ids = Signal::derive(move || {
|
||||
get_comp_ids("Community Involvement")
|
||||
});
|
||||
|
||||
let comm_text = Signal::derive(move || {
|
||||
get_comp_text("Community Involvement")
|
||||
});
|
||||
|
||||
// We'll collect the scholarship's name, num_awards, amount_per_award, total_awards,
|
||||
@@ -546,11 +600,30 @@ fn ScholarshipForm(
|
||||
</Row>
|
||||
<Row>
|
||||
<ChipsList
|
||||
label="Scholarship Requirements"
|
||||
data_member="requirements"
|
||||
data_map=form_data
|
||||
values=comparison_ids
|
||||
displayed_text=comparison_text
|
||||
label="Sports Participation"
|
||||
data_member="sports_participation"
|
||||
data_map=chips_data
|
||||
values=sports_ids
|
||||
displayed_text=sports_text
|
||||
allows_multiple=true
|
||||
/>
|
||||
</Row>
|
||||
<Row>
|
||||
<ChipsList
|
||||
label="Community Involvement"
|
||||
data_member="community_involvement"
|
||||
data_map=chips_data
|
||||
values=comm_ids
|
||||
displayed_text=comm_text
|
||||
/>
|
||||
</Row>
|
||||
<Row>
|
||||
<ChipsList
|
||||
label="Miscellaneous"
|
||||
data_member="misc"
|
||||
data_map=chips_data
|
||||
values=misc_ids
|
||||
displayed_text=misc_text
|
||||
/>
|
||||
</Row>
|
||||
})
|
||||
|
||||
Reference in new issue
Block a user