feat: alter OutlinedTextField to use ValueType enum

Also implemented extra error checking when the values are passed as the incorrect type. For example, the only accepted types are strings and numbers, so that will be checked and handled accordingly.
This commit is contained in:
alphafetus committed 2025-09-19 12:30:27 -04:00
1 parent 0d4bbc9e70
commit 39996319ba
6 files changed
+89 -59

No files matched your search

+16 -11
View File
@@ -1,11 +1,12 @@
use crate::components::MultiEntryData; use crate::components::{MultiEntryData, ValueType};
use macros::Reactive; use macros::Reactive;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::default::Default; use std::default::Default;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use reactive_stores::Store;
/// Represents information about a student. /// Represents information about a student.
#[derive(Serialize, Deserialize, Debug, Clone, Reactive, Default)] #[derive(Serialize, Deserialize, Debug, Clone, Default, Store, Reactive)]
pub struct StudentInfo { pub struct StudentInfo {
/// The email address of the student. This was used in the previous version of the database, /// The email address of the student. This was used in the previous version of the database,
/// and should now be deprecated. /// and should now be deprecated.
@@ -14,32 +15,36 @@ pub struct StudentInfo {
pub Email: String, pub Email: String,
/// The student's first name. /// The student's first name.
#[serde(default)] #[serde(default)]
pub first_name: String, pub first_name: ValueType, // String
/// The student's last name. /// The student's last name.
#[serde(default)] #[serde(default)]
pub last_name: String, pub last_name: ValueType, // String
/// The student's highest math score on the SAT. /// The student's highest math score on the SAT.
#[serde(default)] #[serde(default = "default_number")]
pub math_sat: i32, pub math_sat: ValueType, // Number
/// The student's desired contact email. /// The student's desired contact email.
#[serde(default)] #[serde(default)]
pub contact_email: String, pub contact_email: ValueType, // String
/// The student's address. /// The student's address.
#[serde(default)] #[serde(default)]
pub address: String, pub address: ValueType, // String
/// The student's town of residence. /// The student's town of residence.
#[serde(default)] #[serde(default)]
pub town: String, pub town: ValueType, // String
/// The student's entered phone number. /// The student's entered phone number.
#[serde(default)] #[serde(default)]
pub phone_number: String, // May be stored as a straight number in the future. pub phone_number: ValueType, // May be stored as a straight number in the future, but String
/// The student's birthdate. /// The student's birthdate.
#[serde(default)] #[serde(default)]
pub birth_date: DateTime<Utc>, pub birth_date: DateTime<Utc>,
/// The student's gender. /// The student's gender.
#[serde(default)] #[serde(default)]
pub gender: String, pub gender: ValueType, // String
/// The student's athletic information. /// The student's athletic information.
#[serde(default)] #[serde(default)]
pub athletic_info: Vec<MultiEntryData>, pub athletic_info: Vec<MultiEntryData>,
} }
fn default_number() -> ValueType {
ValueType::Number(0)
}
+2 -2
View File
@@ -9,7 +9,7 @@ use super::{multi_entry_data::*, multi_entry_member::*};
fn render_entry_component(entry_data: MultiEntryData, member_info: MultiEntryMember, label: String) -> impl IntoView { fn render_entry_component(entry_data: MultiEntryData, member_info: MultiEntryMember, label: String) -> impl IntoView {
// Parse the multientry members into the correct components // Parse the multientry members into the correct components
let data = entry_data.data.get(&member_info.member_name); let data = entry_data.data.get(&member_info.member_name);
let reactive = RwSignal::new(data.unwrap().as_string().unwrap()); let reactive = RwSignal::new(data.unwrap().clone());
let temp = either!(member_info.input_type, let temp = either!(member_info.input_type,
InputType::Text => view! { InputType::Text => view! {
<OutlinedTextField <OutlinedTextField
@@ -60,7 +60,7 @@ pub fn Entry(
let data = entry_data.clone(); let data = entry_data.clone();
let input = input_type.clone(); let input = input_type.clone();
view! { view! {
<div>{render_entry_component(data, input, "test".to_owned())}</div> // <div>{render_entry_component(data, input, "test".to_owned())}</div>
} }
}).collect::<Vec<_>>() }).collect::<Vec<_>>()
} }
+6
View File
@@ -59,6 +59,12 @@ impl ValueType {
} }
} }
impl Default for ValueType {
fn default() -> Self {
ValueType::String(String::new())
}
}
impl Display for ValueType { impl Display for ValueType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = match self { let str = match self {
+35 -20
View File
@@ -1,32 +1,42 @@
use leptos::prelude::*; use leptos::prelude::*;
use std::str::FromStr; use crate::components::ValueType;
#[component] #[component]
pub fn OutlinedTextField<T>( pub fn OutlinedTextField(
#[prop(optional, into)] placeholder: T, #[prop(optional, into)] placeholder: String,
#[prop(optional)] value: RwSignal<T>, // #[prop()] value: Subfield<TStore, TInner, ValueType>,
#[prop()] value: RwSignal<ValueType>,
#[prop(optional)] disabled: RwSignal<bool>, #[prop(optional)] disabled: RwSignal<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,
#[prop(optional, into)] input_type: String, #[prop(optional, into)] input_type: String,
) -> impl IntoView #[prop(optional, into)] error_text: RwSignal<String>
where ) -> impl IntoView {
T: Default + 'static + FromStr + ToString + PartialEq + Send + Sync + Clone, // Check if the type is one of the supported formats. This input will only accept a string
{ // or a number value.
// Parsing logic for non-string types which happens on each input.
let on_input = move |e| { let on_input = move |e| {
let input_value = event_target_value(&e); match value.get() {
let can_parse = T::from_str(&input_value); ValueType::String(_) => {
if let Ok(parsed_value) = can_parse { // Define string parse function.
value.set(parsed_value); value.set(ValueType::String(event_target_value(&e)));
} else { },
error.set(true); ValueType::Number(_) => {
value.set(T::default()); // Define number parse function.
let temp = event_target_value(&e);
value.set(ValueType::Number(temp.parse::<i32>().unwrap_or(0)));
},
_ => {
// Create some custom function that reports failure.
let value_type = value.get();
println!("Input {value_type:?} is not a valid type.");
error.set(true);
error_text.set(format!("InputType {value_type:?} is not a valid type."));
}
} }
}; };
view! ( view! {
<div class="flex flex-1"> <div class="flex flex-1">
<label class="flex flex-col flex-1"> <label class="flex flex-col flex-1">
<span class="block ml-1.5 mb-0 font-bold">{label}</span> <span class="block ml-1.5 mb-0 font-bold">{label}</span>
@@ -37,12 +47,17 @@ where
disabled:border-gray-600 disabled:pointer-events-none disabled:bg-gray-600/33" disabled:border-gray-600 disabled:pointer-events-none disabled:bg-gray-600/33"
r#type={input_type} r#type={input_type}
disabled={disabled} disabled={disabled}
placeholder={placeholder.to_string()} placeholder={placeholder}
prop:name=name prop:name=name
prop:value={move || value.get().to_string()} // Sets the initial value. prop:value={move || value.get().to_string()} // Sets the initial value.
on:input=on_input on:input=on_input
/> />
<div
class="text-red-600 text-sm mr-1.5 ml-1.5"
class=(["hidden"], move || !error.get())>
{error_text}
</div>
</label> </label>
</div> </div>
) }
} }
+28 -24
View File
@@ -7,10 +7,11 @@ use serde_dynamo::{from_item, to_item};
use crate::app::Unauthenticated; use crate::app::Unauthenticated;
use crate::common::{StudentInfo, UserClaims}; use crate::common::{StudentInfo, UserClaims};
use crate::components::{ActionButton, Loading, OutlinedTextField, Panel, RadioList, Row, Select}; use crate::components::{ActionButton, Loading, OutlinedTextField, Panel, Row};
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 leptos_oidc::{Algorithm, AuthLoaded, AuthSignal, Authenticated};
use reactive_stores::Store;
use traits::{AsReactive, ReactiveCapture}; use traits::{AsReactive, ReactiveCapture};
/// # Get Student Info /// # Get Student Info
@@ -124,6 +125,7 @@ pub fn HomePage() -> impl IntoView {
server_resource server_resource
.get() .get()
.map(|submission: StudentInfo| { .map(|submission: StudentInfo| {
let store_info = Store::new(submission.clone());
let reactive_info = submission.as_reactive(); let reactive_info = submission.as_reactive();
let elements_disabled = RwSignal::new(false); let elements_disabled = RwSignal::new(false);
let result_msg = Signal::derive(move || { let result_msg = Signal::derive(move || {
@@ -197,34 +199,36 @@ pub fn HomePage() -> impl IntoView {
value=reactive_info.address value=reactive_info.address
/> />
</Row> </Row>
<Row> // <Row>
<RadioList // <RadioList
label="Town:" // label="Town:"
selected=reactive_info.town // selected=reactive_info.town
items=vec!["Southbury", "Middlebury"] // items=vec!["Southbury", "Middlebury"]
.into_iter() // .into_iter()
.map(|s| s.into()) // .map(|s| s.into())
.collect() // .collect()
disabled=elements_disabled // disabled=elements_disabled
/> // />
</Row> // </Row>
<Row> // <Row>
<Select // <Select
label="Gender:" // label="Gender:"
value_list=vec!["Male", "Female", "Prefer not to answer"] // value_list=vec!["Male", "Female", "Prefer not to answer"]
.into_iter() // .into_iter()
.map(|s| s.into()) // .map(|s| s.into())
.collect() // .collect()
value=reactive_info.gender // value=reactive_info.gender
disabled=elements_disabled // disabled=elements_disabled
/> // />
</Row> // </Row>
<Row> <Row>
<ActionButton <ActionButton
on:click=move |_| { on:click=move |_| {
let captured = reactive_info.capture();
console_log(format!("Found values: {captured:?}").as_str());
submit_action submit_action
.dispatch(CreateSampleSubmission { .dispatch(CreateSampleSubmission {
student_info: reactive_info.capture(), student_info: captured,
}); });
} }
disabled=elements_disabled disabled=elements_disabled
+2 -2
View File
@@ -1,6 +1,6 @@
use crate::app::Unauthenticated; use crate::app::Unauthenticated;
use crate::common::UserClaims; use crate::common::UserClaims;
use crate::components::{Loading, MultiEntry, MultiEntryData, MultiEntryMember, OutlinedTextField}; use crate::components::{Loading, MultiEntry, MultiEntryData, MultiEntryMember, OutlinedTextField, ValueType};
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, LogoutLink}; use leptos_oidc::{Algorithm, AuthLoaded, AuthSignal, Authenticated, LogoutLink};
@@ -28,7 +28,7 @@ pub fn ProviderEntry() -> impl IntoView {
placeholder = "Testing information..." placeholder = "Testing information..."
name = "testing_input" name = "testing_input"
label = "This is a testing question." label = "This is a testing question."
value = RwSignal::new("".to_owned()) value = RwSignal::new(ValueType::String("".to_owned()))
/> />
</div> </div>
<div> <div>