Added settings for company info. Added code for entity validation.
							parent
							
								
									44b8b21426
								
							
						
					
					
						commit
						9dd0993204
					
				@ -0,0 +1,48 @@
 | 
			
		||||
use crate::backend::data::Company;
 | 
			
		||||
use leptos::*;
 | 
			
		||||
 | 
			
		||||
#[server(GetCompany, "/api", "Url", "get_company")]
 | 
			
		||||
pub async fn get_company(cx: Scope) -> Result<Company, ServerFnError> {
 | 
			
		||||
    use crate::backend::AppData;
 | 
			
		||||
    use actix_web::web::Data;
 | 
			
		||||
    use leptos_actix::extract;
 | 
			
		||||
 | 
			
		||||
    let pool = extract(
 | 
			
		||||
        cx,
 | 
			
		||||
        |data: Data<AppData>| async move { data.db_pool().clone() },
 | 
			
		||||
    )
 | 
			
		||||
    .await?;
 | 
			
		||||
 | 
			
		||||
    let cmp = sqlx::query_as::<_, Company>("SELECT * FROM company")
 | 
			
		||||
        .fetch_one(&pool)
 | 
			
		||||
        .await?;
 | 
			
		||||
 | 
			
		||||
    Ok(cmp)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[server(UpdateCompany, "/api", "Url", "update_company")]
 | 
			
		||||
pub async fn update_company(cx: Scope, company: Company) -> Result<(), ServerFnError> {
 | 
			
		||||
    use crate::backend::AppData;
 | 
			
		||||
    use actix_web::web::Data;
 | 
			
		||||
    use leptos_actix::extract;
 | 
			
		||||
 | 
			
		||||
    let pool = extract(
 | 
			
		||||
        cx,
 | 
			
		||||
        |data: Data<AppData>| async move { data.db_pool().clone() },
 | 
			
		||||
    )
 | 
			
		||||
    .await?;
 | 
			
		||||
 | 
			
		||||
    sqlx::query(
 | 
			
		||||
        "UPDATE company SET name = $1, street = $2, house_number = $3, zip_code = $4, city = $5 \
 | 
			
		||||
        WHERE id = $6")
 | 
			
		||||
    .bind(company.name.clone())
 | 
			
		||||
    .bind(company.street.clone())
 | 
			
		||||
    .bind(company.house_number.clone())
 | 
			
		||||
    .bind(company.zip_code.clone())
 | 
			
		||||
    .bind(company.city.clone())
 | 
			
		||||
    .bind(company.id())
 | 
			
		||||
    .execute(&pool)
 | 
			
		||||
    .await?;
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
@ -1,2 +1,4 @@
 | 
			
		||||
pub mod modal_box;
 | 
			
		||||
pub mod server_err;
 | 
			
		||||
pub mod validation_err;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,27 @@
 | 
			
		||||
use crate::components::modal_box::DialogOpener;
 | 
			
		||||
use leptos::*;
 | 
			
		||||
 | 
			
		||||
#[component]
 | 
			
		||||
pub fn ServerErr(
 | 
			
		||||
    cx: Scope,
 | 
			
		||||
    result: RwSignal<Option<Result<(), ServerFnError>>>,
 | 
			
		||||
    opener: DialogOpener,
 | 
			
		||||
) -> impl IntoView {
 | 
			
		||||
    view! {cx, {move || {
 | 
			
		||||
        if let Some(val) = result.get() {
 | 
			
		||||
            if let Err(e) = val {
 | 
			
		||||
                view! {cx,
 | 
			
		||||
                <div class="alert alert-danger">
 | 
			
		||||
                    "Server error: " {e.to_string()}
 | 
			
		||||
                </div>
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                opener.hide();
 | 
			
		||||
                view! {cx, <div></div>}
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            view! {cx, <div></div>}
 | 
			
		||||
        }
 | 
			
		||||
    }}
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,37 @@
 | 
			
		||||
use leptos::*;
 | 
			
		||||
use crate::locales::trl;
 | 
			
		||||
use crate::validator::Validator;
 | 
			
		||||
 | 
			
		||||
#[component]
 | 
			
		||||
pub fn ValidationErr(
 | 
			
		||||
    cx: Scope,
 | 
			
		||||
    validator: Validator,
 | 
			
		||||
) -> impl IntoView {
 | 
			
		||||
    view! {cx, {move || {
 | 
			
		||||
        if !validator.is_valid() {
 | 
			
		||||
            if let Some(msgs) = validator.messages() {
 | 
			
		||||
                let out_msgs = msgs.into_iter().map(move |e| {
 | 
			
		||||
                    view! {cx,
 | 
			
		||||
                        <div class="alert alert-danger">
 | 
			
		||||
                            {trl(cx, &e)}
 | 
			
		||||
                        </div>
 | 
			
		||||
                    }
 | 
			
		||||
                }).collect_view(cx);
 | 
			
		||||
                view! {cx,
 | 
			
		||||
                    <div>
 | 
			
		||||
                        {out_msgs}
 | 
			
		||||
                    </div>
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                view! {cx,
 | 
			
		||||
                <div class="alert alert-danger">
 | 
			
		||||
                    "Validation error"
 | 
			
		||||
                </div>
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            view! {cx, <div></div>}
 | 
			
		||||
        }
 | 
			
		||||
    }}
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,15 +1,19 @@
 | 
			
		||||
use std::borrow::Cow;
 | 
			
		||||
use std::ops::Deref;
 | 
			
		||||
use leptos::*;
 | 
			
		||||
use crate::locales::catalogues::get_dictionary;
 | 
			
		||||
 | 
			
		||||
mod catalogues;
 | 
			
		||||
 | 
			
		||||
pub fn trl(cx: Scope, phrase: &'static str) -> impl Fn() -> &'static str {
 | 
			
		||||
pub fn trl(cx: Scope, phrase: &str) -> impl Fn() -> String {
 | 
			
		||||
    let mut translated = phrase;
 | 
			
		||||
    if let Some(dict) = get_dictionary(cx) {
 | 
			
		||||
        if let Some(p) = dict.get(phrase) {
 | 
			
		||||
        if let Some(p) = dict.get(phrase.to_string().as_str()) {
 | 
			
		||||
            translated = *p;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    || { translated }
 | 
			
		||||
    let out = translated.to_string();
 | 
			
		||||
 | 
			
		||||
    move || { out.clone() }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,119 @@
 | 
			
		||||
use crate::backend::data::Company;
 | 
			
		||||
use crate::backend::company::UpdateCompany;
 | 
			
		||||
use crate::components::modal_box::{
 | 
			
		||||
    DialogOpener, DlgNotLoaded, ModalBody, ModalDialog, ModalFooter,
 | 
			
		||||
};
 | 
			
		||||
use crate::components::server_err::ServerErr;
 | 
			
		||||
use crate::locales::trl;
 | 
			
		||||
use crate::validator::Validator;
 | 
			
		||||
use leptos::*;
 | 
			
		||||
use leptos_router::*;
 | 
			
		||||
use crate::components::validation_err::ValidationErr;
 | 
			
		||||
 | 
			
		||||
#[component]
 | 
			
		||||
pub fn CompanyEdit(
 | 
			
		||||
    cx: Scope,
 | 
			
		||||
    company: ReadSignal<Option<Company>>,
 | 
			
		||||
    opener: DialogOpener,
 | 
			
		||||
) -> impl IntoView {
 | 
			
		||||
    view! {cx,
 | 
			
		||||
        {move ||
 | 
			
		||||
            if let Some(c) = company.get() {
 | 
			
		||||
                let update_company = create_server_action::<UpdateCompany>(cx);
 | 
			
		||||
                let upd_val = update_company.value();
 | 
			
		||||
                let validator = Validator::new(cx);
 | 
			
		||||
                view! {cx,
 | 
			
		||||
                    <ActionForm
 | 
			
		||||
                        on:submit=move |ev| {
 | 
			
		||||
                            let act = UpdateCompany::from_event(&ev);
 | 
			
		||||
                            if !act.is_err() {
 | 
			
		||||
                                validator.check(&act.unwrap().company, &ev);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        action=update_company>
 | 
			
		||||
                    <ModalDialog opener=opener title="Edit company">
 | 
			
		||||
                    <ModalBody>
 | 
			
		||||
                        <ServerErr result={upd_val} opener=opener/>
 | 
			
		||||
                        <ValidationErr validator=validator />
 | 
			
		||||
                        <input type="hidden" value=c.id() name="company[id]"/>
 | 
			
		||||
                        <div class="row">
 | 
			
		||||
                            <div class="col mb-3">
 | 
			
		||||
                                <label  for="nameWithTitle" class="form-label">"Name"</label>
 | 
			
		||||
                                <input
 | 
			
		||||
                                    type="text"
 | 
			
		||||
                                    id="nameWithTitle"
 | 
			
		||||
                                    class="form-control"
 | 
			
		||||
                                    placeholder="Enter Name"
 | 
			
		||||
                                    value=c.name
 | 
			
		||||
                                    name="company[name]"
 | 
			
		||||
                                />
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="row">
 | 
			
		||||
                            <div class="col mb-3">
 | 
			
		||||
                                <label  for="street" class="form-label">"Street"</label>
 | 
			
		||||
                                <input
 | 
			
		||||
                                    type="text"
 | 
			
		||||
                                    id="street"
 | 
			
		||||
                                    class="form-control"
 | 
			
		||||
                                    placeholder="Enter Street"
 | 
			
		||||
                                    value=c.street
 | 
			
		||||
                                    name="company[street]"
 | 
			
		||||
                                />
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="col-4 mb-3">
 | 
			
		||||
                                <label  for="houseNumber" class="form-label">"House number"</label>
 | 
			
		||||
                                <input
 | 
			
		||||
                                    type="text"
 | 
			
		||||
                                    id="houseNumber"
 | 
			
		||||
                                    class="form-control"
 | 
			
		||||
                                    placeholder="Enter House number"
 | 
			
		||||
                                    value=c.house_number
 | 
			
		||||
                                    name="company[house_number]"
 | 
			
		||||
                                />
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="row">
 | 
			
		||||
                            <div class="col-4 mb-3">
 | 
			
		||||
                                <label  for="zip" class="form-label">"ZIP code"</label>
 | 
			
		||||
                                <input
 | 
			
		||||
                                    type="text"
 | 
			
		||||
                                    id="zip"
 | 
			
		||||
                                    class="form-control"
 | 
			
		||||
                                    placeholder="Enter ZIP code"
 | 
			
		||||
                                    value=c.zip_code
 | 
			
		||||
                                    name="company[zip_code]"
 | 
			
		||||
                                />
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="col mb-3">
 | 
			
		||||
                                <label  for="city" class="form-label">"City"</label>
 | 
			
		||||
                                <input
 | 
			
		||||
                                    type="text"
 | 
			
		||||
                                    id="city"
 | 
			
		||||
                                    class="form-control"
 | 
			
		||||
                                    placeholder="Enter City"
 | 
			
		||||
                                    value=c.city
 | 
			
		||||
                                    name="company[city]"
 | 
			
		||||
                                />
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </ModalBody>
 | 
			
		||||
                    <ModalFooter>
 | 
			
		||||
                        <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal" on:click=move |_| opener.hide()>
 | 
			
		||||
                            {trl(cx, "Close")}
 | 
			
		||||
                        </button>
 | 
			
		||||
                        <button type="submit" class="btn btn-primary">
 | 
			
		||||
                            {trl(cx, "Save changes")}
 | 
			
		||||
                        </button>
 | 
			
		||||
                    </ModalFooter>
 | 
			
		||||
                </ModalDialog>
 | 
			
		||||
                </ActionForm>
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                view! {cx,
 | 
			
		||||
                <DlgNotLoaded opener=opener title="Edit company" />
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,43 @@
 | 
			
		||||
use leptos::*;
 | 
			
		||||
use serde::de::Unexpected::Option;
 | 
			
		||||
use crate::backend::company::get_company;
 | 
			
		||||
use crate::components::modal_box::DialogOpener;
 | 
			
		||||
use crate::locales::trl;
 | 
			
		||||
use crate::pages::company_edit::CompanyEdit;
 | 
			
		||||
 | 
			
		||||
#[component]
 | 
			
		||||
pub fn CompanyInfo(cx: Scope) -> impl IntoView {
 | 
			
		||||
    let editor = DialogOpener::new(cx);
 | 
			
		||||
    let company = create_resource(cx, move|| editor.visible(), move |_| { get_company(cx) });
 | 
			
		||||
    let (cmp, set_cmp) = create_signal(cx, None);
 | 
			
		||||
 | 
			
		||||
    view! {cx,
 | 
			
		||||
        <CompanyEdit company={cmp} opener=editor/>
 | 
			
		||||
      <div class="card">
 | 
			
		||||
        <div class="card-body">
 | 
			
		||||
          <h5 class="card-title"><i class="bx bx-buildings"></i>" "{trl(cx, "Company info")}</h5>
 | 
			
		||||
          <p class="card-text">
 | 
			
		||||
            <Transition fallback=move || view! {cx, <p>{trl(cx, "Loading...")}</p> }>
 | 
			
		||||
                {move || {
 | 
			
		||||
                    company.read(cx).map(|c| match c {
 | 
			
		||||
                        Err(e) => {view! {cx, <p>{trl(cx, "Error loading data")}</p>
 | 
			
		||||
                            <p>{e.to_string()}</p>
 | 
			
		||||
                        }}
 | 
			
		||||
                        Ok(c) => {
 | 
			
		||||
                            set_cmp.update(|cmp| *cmp = Some(c.clone()));
 | 
			
		||||
                            view! {
 | 
			
		||||
                            cx, <p><b>{c.name}</b></p>
 | 
			
		||||
                            <p>{c.street}" "{c.house_number}<br/>
 | 
			
		||||
                            {c.zip_code}" "{c.city}
 | 
			
		||||
                            </p>
 | 
			
		||||
                        }}
 | 
			
		||||
                    })
 | 
			
		||||
                }}
 | 
			
		||||
            </Transition>
 | 
			
		||||
          </p>
 | 
			
		||||
          <a href="javascript:void(0)" class="card-link" on:click = move |_| editor.show()>
 | 
			
		||||
                <i class="bx bx-pencil fs-4 lh-0"></i></a>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1 +1,4 @@
 | 
			
		||||
pub mod home_page;
 | 
			
		||||
pub mod settings;
 | 
			
		||||
pub mod company_info;
 | 
			
		||||
mod company_edit;
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,45 @@
 | 
			
		||||
use leptos::*;
 | 
			
		||||
use crate::locales::trl;
 | 
			
		||||
use crate::pages::company_info::CompanyInfo;
 | 
			
		||||
 | 
			
		||||
#[component]
 | 
			
		||||
pub fn Settings(cx: Scope) -> impl IntoView {
 | 
			
		||||
    view! {cx,
 | 
			
		||||
        <h1>{trl(cx, "Settings")}</h1>
 | 
			
		||||
        <div class="row mb-5">
 | 
			
		||||
            <div class="col-md-6 col-lg-4 mb-3">
 | 
			
		||||
                <CompanyInfo/>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="col-md-6 col-lg-4 mb-3">
 | 
			
		||||
              <div class="card h-100">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                  <h5 class="card-title">"Card title"</h5>
 | 
			
		||||
                  <h6 class="card-subtitle text-muted">"Support card subtitle"</h6>
 | 
			
		||||
                </div>
 | 
			
		||||
                <img class="img-fluid" src="../assets/img/elements/13.jpg" alt="Card image cap" />
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                  <p class="card-text">"Bear claw sesame snaps gummies chocolate."</p>
 | 
			
		||||
                  <a href="javascript:void(0);" class="card-link">"Card link"</a>
 | 
			
		||||
                  <a href="javascript:void(0);" class="card-link">"Another link"</a>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="col-md-6 col-lg-4 mb-3">
 | 
			
		||||
              <div class="card h-100">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                  <h5 class="card-title">"Card title"</h5>
 | 
			
		||||
                  <h6 class="card-subtitle text-muted">"Support card subtitle"</h6>
 | 
			
		||||
                  <img
 | 
			
		||||
                    class="img-fluid d-flex mx-auto my-4"
 | 
			
		||||
                    src="../assets/img/elements/4.jpg"
 | 
			
		||||
                    alt="Card image cap"
 | 
			
		||||
                  />
 | 
			
		||||
                  <p class="card-text">"Bear claw sesame snaps gummies chocolate."</p>
 | 
			
		||||
                  <a href="javascript:void(0);" class="card-link">Card link</a>
 | 
			
		||||
                  <a href="javascript:void(0);" class="card-link">Another link</a>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,61 @@
 | 
			
		||||
use leptos::*;
 | 
			
		||||
use validator::Validate;
 | 
			
		||||
 | 
			
		||||
#[derive(Copy, Clone)]
 | 
			
		||||
pub struct Validator {
 | 
			
		||||
    message: ReadSignal<Option<String>>,
 | 
			
		||||
    set_message: WriteSignal<Option<String>>,
 | 
			
		||||
    valid: ReadSignal<bool>,
 | 
			
		||||
    set_valid: WriteSignal<bool>,
 | 
			
		||||
    messages: ReadSignal<Option<Vec<String>>>,
 | 
			
		||||
    set_messages: WriteSignal<Option<Vec<String>>>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Validator {
 | 
			
		||||
    pub fn new(cx: Scope) -> Self {
 | 
			
		||||
        let (valid, set_valid) = create_signal(cx, true);
 | 
			
		||||
        let (message, set_message) = create_signal(cx, None);
 | 
			
		||||
        let (messages, set_messages) = create_signal(cx, None);
 | 
			
		||||
        Self {
 | 
			
		||||
            message,
 | 
			
		||||
            set_message,
 | 
			
		||||
            valid,
 | 
			
		||||
            set_valid,
 | 
			
		||||
            messages,
 | 
			
		||||
            set_messages
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn check(&self, entity: &impl Validate, ev: &web_sys::Event) {
 | 
			
		||||
        if let Err(val_err) = entity.validate() {
 | 
			
		||||
            ev.prevent_default();
 | 
			
		||||
            self.set_message.update(|m| *m = Some(val_err.to_string().clone()));
 | 
			
		||||
            self.set_messages.update(|m| *m = {
 | 
			
		||||
                let mut out: Vec<String> = vec![];
 | 
			
		||||
                val_err.field_errors().drain().for_each(|e| {
 | 
			
		||||
                    e.1.to_vec().into_iter().for_each(|err| {
 | 
			
		||||
                        if let Some(msg) = err.message {
 | 
			
		||||
                            out.push(msg.to_string());
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
                Some(out)
 | 
			
		||||
            });
 | 
			
		||||
            self.set_valid.update(|v| *v = false);
 | 
			
		||||
        } else {
 | 
			
		||||
            self.set_valid.update(|v| *v = true);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn is_valid(&self) -> bool {
 | 
			
		||||
        self.valid.get()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn message(&self) -> Option<String> {
 | 
			
		||||
        self.message.get()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn messages(&self) -> Option<Vec<String>> {
 | 
			
		||||
        self.messages.get()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
					Loading…
					
					
				
		Reference in New Issue