use std::collections::HashMap; use crate::components::ActionButton; use leptos::prelude::*; use macros::Reactive; use serde::{Deserialize, Serialize}; use uuid::Uuid; #[derive(Clone, Serialize, Deserialize, Debug, Reactive, Default)] pub struct MultiEntryData { pub id: Uuid, pub data: HashMap, } impl MultiEntryData { /// Creates a new MultiEntryData struct. pub fn new() -> Self { Self { id: Uuid::new_v4(), data: HashMap::new(), } } } /// Contains all information required to display an entry in the MultiEntry component. #[derive(Clone, Serialize, Deserialize, Debug)] pub struct MultiEntryMember { /// The name of the member, used for display only. pub display_name: String, /// The actual name of the member, used to get the data from an Entry struct. pub member_name: String, } impl MultiEntryMember { /// Creates a new MultiEntryMember struct. pub fn _new() -> Self { Self { display_name: String::new(), member_name: String::new(), } } /// Creates a new MultiEntryMember struct with the specified information. pub fn _new_with_data(display_name: String, member_name: String) -> Self { Self { display_name, member_name, } } /// Creates a new MultiEntryMember struct with the specified information. Uses string slices. pub fn from_str(display_name: &str, member_name: &str) -> Self { Self { display_name: display_name.into(), member_name: member_name.into(), } } } #[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()] entry_data: MultiEntryData, /// The main display member for this entry. #[prop()] name_member: Signal, /// The secondary display member for this entry. #[prop()] info_member: Signal, ) -> impl IntoView { let name_data = entry_data.data.get(&name_member.get().member_name).unwrap(); let info_data = entry_data.data.get(&info_member.get().member_name).unwrap(); view! { // This div MUST have the same spacing CSS as the second-level header div
{name_data.as_string()} {info_data.as_string()}
} } #[component] pub fn MultiEntry( /// The list of entries to use. #[prop()] entries: RwSignal>, /// the main display member for each entry object. #[prop()] name_member: Signal, /// The secondary display member for each entry object. #[prop()] info_member: Signal, ) -> impl IntoView { // Creates an entry in the given entries list. let create_entry = move |_| { let mut new_entry = MultiEntryData::new(); new_entry.data.insert( name_member.get().member_name, ValueType::String(String::from("Testing")), ); new_entry.data.insert( info_member.get().member_name, ValueType::String(new_entry.id.to_string()), ); entries.update(|entry_vec| entry_vec.push(new_entry)); }; view! {
// Header section
// This div MUST have the same spacing CSS as the main entry div
{name_member.get().display_name} {info_member.get().display_name}
// Entry section
} } />
Add Entry } }