chore: PC change (create multientry)
This commit is contained in:
1 parent
127cf17c90
commit
9d18eb0810
8 files changed
+302
-5
No files matched your search
@@ -0,0 +1,3 @@
|
|||||||
|
brace_style = "PreferSameLine"
|
||||||
|
indent_style = "Visual"
|
||||||
|
tab_spaces = 4
|
||||||
+136
@@ -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.
|
||||||
@@ -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
@@ -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::*
|
||||||
};
|
};
|
||||||
@@ -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::*
|
||||||
|
};
|
||||||
@@ -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>
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user