chore: PC change (create multientry)

This commit is contained in:
alphafetus committed 2025-07-27 17:53:45 -04:00
1 parent 127cf17c90
commit 9d18eb0810
8 files changed
+302 -5

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
brace_style = "PreferSameLine"
indent_style = "Visual"
tab_spaces = 4
+136
View File
@@ -0,0 +1,136 @@
Forms should be built for this application with a series of JSON-type files.
Each form will have a series of sections, and each section will have a series of
questions. Each question has a type and a label - question styles may come later.
It may be possible for us to call out specific question types without needing
to parse the types separately, but that feels not-really-worth-it.
Example:
```
{
"SectionDefinitions": [
"SectionTitle": [
"text": {
"label": "First name question!",
"placeholder": "John",
},
"text": {
"label": "Last name question!",
"placeholder": "Smith",
}
],
"Section Two": [
"text": {
"label": "Section two testing question",
"placeholder": "Some example here",
},
"select": {
"label": "This is a dropdown element!",
"placeholder": "option1",
"possibleValues": [
"option1": "something",
"option2": "another something",
]
}
]
]
}
```
...and so on. Here's a likely set of structures to use:
```
struct FormDefinitionDoc {
Vec<Section> section_defs;
// and any extra values that should be applied
// across the entire form, like settings.
}
struct Section {
Vec<QuestionComponent> questions;
// each section may have a series of settings including how to submit,
// when to submit, and where to submit to at the very least.
}
struct QuestionComponent {
label: String,
placeholder: String,
possible_values: HashMap<str, str>,
// there are definitely values that i'll find while creating this structure.
}
```
Types of questions that are required, starting with the providers.
Providers must be able to:
- Log into the website using a username and password (handled by AWS)
- Create and manage multiple scholarships.
- Input scholarship information for each scholarship.
- View students that have applied.
- View student application information (should just be labels??)
- Buttons are a given. Duh.
Students must be able to:
- Log in using their Region 15 email (handled by AWS)
- Create and manage their general application.
- Create and manage applications specific to a single scholarship.
- View their acceptance status (need more confirmation)
Administrators must be able to:
- Log in using their Region 15 email (handled by AWS)
- Manage student scholarship applications.
- Manage scholarship provider account information.
- Create/manage scholarship information.
- Download student applications if needed.
- Statistics would be nice, but are likely a future thing.
Right off the bad, I think I can see a few types of buttons and text inputs,
along with dropdowns, checkboxes, our custom `MultiEntry` element from last
time. That means no new options, but it does mean that the `FormDefinition`
needs to be able to keep track of all this information.
These are some example `QuestionComponent` objects:
Text fields:
```
{
question_type: "TEXT" | "EMAIL" | "NUMBER",
label: "The question label",
name: "input_name[jsdoc]",
placeholder: "An example input"
}
```
Dropdowns/checkboxes/radios:
```
{
question_type: "DROPDOWN" | "CHECKBOX" | "RADIO",
label: "The question label",
name: "input_name[jsdoc]",
placeholder: "The pre-selected option value",
options: [
"optionValue": "Option Text"
]
}
```
`MultiEntry`:
```
{
question_type: "MULTI",
label: "The question label",
name: "input_name[jsdoc]"
}
```
The real question here: how can we store this information in a database? And
this means specifically within DynamoDB, because this is not an easy structure
to store in there.
That's going to be the hardest apart and I'll likely involve the AI in that
planning.
+38
View File
@@ -0,0 +1,38 @@
use std::collections::HashMap;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
struct FormDefinition {
sections: Vec<Section>
}
#[derive(Serialize, Deserialize)]
struct Section {
questions: Vec<QuestionType>
}
#[derive(Serialize, Deserialize)]
enum QuestionType {
Text,
Email,
Number,
Dropdown,
Checkbox,
Radio,
Multi
}
struct TextInput {
}
#[derive(Serialize, Deserialize)]
struct QuestionComponent {
question_type: QuestionType,
label: Option<String>,
placeholder: Option<String>,
// Used in <select> and <checkbox> elements
options: Option<HashMap<String, String>>
}
+3 -1
View File
@@ -1,7 +1,9 @@
mod loaner_user; mod loaner_user;
mod student_info; mod student_info;
mod form_definition;
pub use self::{ pub use self::{
loaner_user::*, loaner_user::*,
student_info::* student_info::*,
form_definition::*
}; };
+6 -1
View File
@@ -1,4 +1,9 @@
mod action_button; mod action_button;
mod text_field; mod text_field;
mod multi_entry;
pub use self::{action_button::ActionButton, text_field::OutlinedTextField}; pub use self::{
action_button::*,
text_field::*,
multi_entry::*
};
+105
View File
@@ -0,0 +1,105 @@
use std::collections::HashMap;
use leptos::{leptos_dom::logging::console_log, prelude::*};
use serde::{Deserialize, Serialize};
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct MultiEntryData {
pub id: usize,
pub data: HashMap<String, ValueType>,
}
impl MultiEntryData {
pub fn new() -> Self {
Self {
id: 0,
data: HashMap::new(),
}
}
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub enum ValueType {
String(String),
Number(i32),
}
impl ValueType {
pub fn as_string(&self) -> String {
match self {
ValueType::String(s) => s.clone(),
ValueType::Number(n) => n.to_string(),
}
}
}
/// Represents a single entry in a MultiEntry component.
/// These are displayed in a list, and function a little bit like a dropdown.
/// When clicked, they expand to show all the inputs, which can be edited.
#[component]
pub fn Entry(
/// The data for this entry.
#[prop()]
entryData: MultiEntryData,
/// The main display member for this entry.
#[prop()]
nameMember: String,
/// The secondary display member for this entry.
#[prop()]
infoMember: String,
) -> impl IntoView {
let nameData = entryData.data.get(&nameMember).unwrap();
let infoData = entryData.data.get(&infoMember).unwrap();
view! {
<div>
<p>{nameData.as_string()}</p>
<p>{infoData.as_string()}</p>
</div>
}
}
#[component]
pub fn MultiEntry(
/// the main display member for each entry object.
#[prop()]
nameMember: String,
/// The secondary display member for each entry object.
#[prop()]
infoMember: String,
) -> impl IntoView {
let entries: Signal<Vec<MultiEntryData>> = Signal::from(vec![]);
let nameKey = nameMember.clone();
let infoKey = infoMember.clone();
let create_entry = move |_| {
console_log("Found button click");
let mut new_entry = MultiEntryData::new();
new_entry.id = entries.get().len() as usize;
new_entry
.data
.insert(nameKey.clone(), ValueType::String(String::from("Testing")));
new_entry
.data
.insert(infoKey.clone(), ValueType::Number(new_entry.id as i32));
entries.get().push(new_entry);
console_log(format!("{:?}", entries.get()).as_str());
};
view! {
<div>
// Entries will go here for right now.
<For
each = move || entries.get()
key = |entry: &MultiEntryData| entry.id
children = move |entry| view! {
<Entry entryData=entry.clone() nameMember=nameMember.clone() infoMember=infoMember.clone()/>
}
/>
</div>
<button on:click=create_entry>
Add Entry
</button>
}
}
+3 -2
View File
@@ -7,6 +7,7 @@ pub fn OutlinedTextField(
#[prop(default = RwSignal::new(false))] disabled: RwSignal<bool>, #[prop(default = RwSignal::new(false))] disabled: RwSignal<bool>,
#[prop(default = String::from(""))] name: String, #[prop(default = String::from(""))] name: String,
#[prop(default = String::from(""))] label: String, #[prop(default = String::from(""))] label: String,
#[prop(default = String::from(""))] input_type: String,
) -> impl IntoView { ) -> impl IntoView {
view! ( view! (
<label> <label>
@@ -15,8 +16,8 @@ pub fn OutlinedTextField(
class="border-2 m-1.5 p-1.5 rounded-md bg-transparent mt-6 relative transition-all duration-400" class="border-2 m-1.5 p-1.5 rounded-md bg-transparent mt-6 relative transition-all duration-400"
class=(["border-red-700"], move || !disabled.get()) class=(["border-red-700"], move || !disabled.get())
class=(["border-gray-600", "pointer-events-none"], move || disabled.get()) class=(["border-gray-600", "pointer-events-none"], move || disabled.get())
r#type="text" r#type={input_type}
disabled={move || disabled.get()} disabled={disabled}
placeholder={placeholder} placeholder={placeholder}
prop:name=name prop:name=name
bind:value=value // This is a two-way binding - it is allowed to read *and* write. bind:value=value // This is a two-way binding - it is allowed to read *and* write.
+8 -1
View File
@@ -1,5 +1,5 @@
use crate::components::{MultiEntry, OutlinedTextField};
use leptos::prelude::*; use leptos::prelude::*;
use crate::components::OutlinedTextField;
#[component] #[component]
pub fn ProviderEntry() -> impl IntoView { pub fn ProviderEntry() -> impl IntoView {
@@ -13,5 +13,12 @@ pub fn ProviderEntry() -> impl IntoView {
label = "This is a testing question.".into() label = "This is a testing question.".into()
/> />
</div> </div>
<div>
<p>This is an example of the multientry element.</p>
<MultiEntry
nameMember="name".into()
infoMember="info".into()
/>
</div>
) )
} }