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