Implemented settings for properties of reservation. Fixed bugs on empty database.
This commit is contained in:
@@ -1,8 +1,29 @@
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
use validator::Validate;
|
||||
use crate::backend::data::{ApiResponse, Company};
|
||||
use crate::components::data_form::ForValidation;
|
||||
|
||||
cfg_if! { if #[cfg(feature = "ssr")] {
|
||||
use sqlx::{query_as, PgPool, query};
|
||||
use crate::error::AppError;
|
||||
use log::info;
|
||||
|
||||
pub async fn check_company(pool: &PgPool) -> Result<(), AppError> {
|
||||
let count: (i64,) = query_as("SELECT COUNT(id) FROM company")
|
||||
.fetch_one(pool)
|
||||
.await?;
|
||||
|
||||
if count.0 == 0 {
|
||||
info!("Creating initial company");
|
||||
query("INSERT INTO company(name, street, house_number, zip_code, city) VALUES('Company name', '', '', '', '')")
|
||||
.execute(pool).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}}
|
||||
|
||||
#[server(GetCompany, "/api", "Url", "get_company")]
|
||||
pub async fn get_company() -> Result<ApiResponse<Company>, ServerFnError> {
|
||||
use crate::backend::AppData;
|
||||
|
||||
+45
-11
@@ -2,9 +2,11 @@
|
||||
//use rust_decimal::Decimal;
|
||||
#![allow(unused_variables)]
|
||||
|
||||
use std::fmt::Display;
|
||||
use chrono::{NaiveTime, Weekday};
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use rust_decimal::Decimal;
|
||||
use serde::{Deserialize, Serialize};
|
||||
//use uuid::Uuid;
|
||||
use validator::{Validate, ValidationError};
|
||||
@@ -162,19 +164,21 @@ impl DayHours {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for DayHours {
|
||||
fn to_string(&self) -> String {
|
||||
impl Display for DayHours {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if self.0.is_empty() {
|
||||
return "".to_string()
|
||||
return write!(f, "{}", "".to_string())
|
||||
}
|
||||
|
||||
self.0.iter().map(|h| {
|
||||
let str = self.0.iter().map(|h| {
|
||||
let discount = if let Some(d) = h.discount() {
|
||||
format!(" ({})", d).to_string()
|
||||
} else { "".to_string() };
|
||||
format!("{} - {}{}", h.from().format("%H:%M"), h.to().format("%H:%M"), discount).to_string()})
|
||||
format!("{} - {}{}", h.from().format("%H:%M"), h.to().format("%H:%M"), discount).to_string()
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
.join(", ");
|
||||
write!(f, "{}", str)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,13 +293,43 @@ impl WeekHours {
|
||||
}
|
||||
}
|
||||
|
||||
/*pub struct Property {
|
||||
id: u16,
|
||||
name: String,
|
||||
description: String,
|
||||
price: Decimal
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Default)]
|
||||
#[cfg_attr(feature = "ssr", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "ssr", sqlx(type_name = "slot_type"))]
|
||||
pub enum SlotType {
|
||||
Quarter,
|
||||
Half,
|
||||
#[default]
|
||||
Hour,
|
||||
Day
|
||||
}
|
||||
|
||||
fn def_true() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Validate, Default)]
|
||||
#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]
|
||||
pub struct ResProperty {
|
||||
id: i32,
|
||||
#[validate(length(min = 1,message = "Name cannot be empty"))]
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub price: Decimal,
|
||||
pub slot: SlotType,
|
||||
#[serde(default = "def_true")]
|
||||
pub allow_multi: bool,
|
||||
#[serde(default = "def_true")]
|
||||
pub active: bool
|
||||
}
|
||||
|
||||
impl ResProperty {
|
||||
pub fn id(&self) -> i32 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
pub enum MessageType {
|
||||
NewReservation,
|
||||
NewReservationCust,
|
||||
|
||||
@@ -4,6 +4,7 @@ pub mod company;
|
||||
pub mod user;
|
||||
pub mod auth_middleware;
|
||||
pub mod opening_hours;
|
||||
pub mod property;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! perm_check {
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
use leptos::*;
|
||||
use validator::Validate;
|
||||
use crate::backend::data::{ApiResponse, ResProperty};
|
||||
use crate::components::data_form::ForValidation;
|
||||
|
||||
#[server]
|
||||
pub async fn get_properties() -> Result<ApiResponse<Vec<ResProperty>>, ServerFnError> {
|
||||
use crate::backend::get_pool;
|
||||
|
||||
let pool = get_pool().await?;
|
||||
let props = sqlx::query_as::<_, ResProperty>("SELECT * FROM property").fetch_all(&pool).await?;
|
||||
|
||||
Ok(ApiResponse::Data(props))
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn create_property(property: ResProperty) ->Result<ApiResponse<()>, ServerFnError> {
|
||||
use crate::backend::get_pool;
|
||||
use crate::perm_check;
|
||||
|
||||
perm_check!(is_admin);
|
||||
|
||||
let pool = get_pool().await?;
|
||||
sqlx::query("INSERT INTO property(name, description, price, slot) VALUES($1, $2, $3, $4)")
|
||||
.bind(&property.name)
|
||||
.bind(&property.description)
|
||||
.bind(&property.price)
|
||||
.bind(&property.slot)
|
||||
.execute(&pool)
|
||||
.await?;
|
||||
|
||||
Ok(ApiResponse::Data(()))
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn edit_property(property: ResProperty) -> Result<ApiResponse<()>, ServerFnError> {
|
||||
use crate::backend::get_pool;
|
||||
use crate::perm_check;
|
||||
|
||||
perm_check!(is_admin);
|
||||
|
||||
let pool = get_pool().await?;
|
||||
sqlx::query("UPDATE property SET name = $1, description = $2, price = $3, active = $4, slot = $5 WHERE id = $6")
|
||||
.bind(&property.name)
|
||||
.bind(&property.description)
|
||||
.bind(&property.price)
|
||||
.bind(property.active)
|
||||
.bind(&property.slot)
|
||||
.bind(property.id())
|
||||
.execute(&pool)
|
||||
.await?;
|
||||
|
||||
Ok(ApiResponse::Data(()))
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn delete_property(id: i32) -> Result<ApiResponse<()>, ServerFnError> {
|
||||
use crate::backend::get_pool;
|
||||
use crate::perm_check;
|
||||
|
||||
perm_check!(is_admin);
|
||||
|
||||
let pool = get_pool().await?;
|
||||
sqlx::query("DELETE FROM property WHERE id = $1")
|
||||
.bind(id)
|
||||
.execute(&pool)
|
||||
.await?;
|
||||
|
||||
Ok(ApiResponse::Data(()))
|
||||
}
|
||||
|
||||
impl ForValidation for CreateProperty {
|
||||
fn entity(&self) -> &dyn Validate {
|
||||
&self.property
|
||||
}
|
||||
}
|
||||
|
||||
impl ForValidation for EditProperty {
|
||||
fn entity(&self) -> &dyn Validate {
|
||||
&self.property
|
||||
}
|
||||
}
|
||||
+7
-1
@@ -9,6 +9,7 @@ cfg_if! { if #[cfg(feature = "ssr")] {
|
||||
use actix_session::*;
|
||||
use leptos_actix::{extract, redirect};
|
||||
use log::{info, warn};
|
||||
use crate::error::AppError;
|
||||
|
||||
pub async fn has_admin_user(pool: &PgPool) -> Result<bool, Error> {
|
||||
let count: (i64,) = query_as(r#"SELECT COUNT(id) FROM "user" WHERE admin = $1"#)
|
||||
@@ -19,7 +20,7 @@ cfg_if! { if #[cfg(feature = "ssr")] {
|
||||
Ok(count.0 > 0)
|
||||
}
|
||||
|
||||
pub async fn create_admin(pool: &PgPool) -> Result<(), Error> {
|
||||
pub async fn create_admin(pool: &PgPool) -> Result<(), AppError> {
|
||||
if !has_admin_user(pool).await? {
|
||||
let pwd = pwhash::bcrypt::hash("admin");
|
||||
query(r#"INSERT INTO "user"(login, password, full_name, admin) VALUES($1, $2, $3, $4)"#)
|
||||
@@ -44,6 +45,11 @@ cfg_if! { if #[cfg(feature = "ssr")] {
|
||||
extract(|session: Session| async move {
|
||||
session.get::<User>("user").unwrap_or(None)
|
||||
}).await.unwrap_or(None)
|
||||
/*let mut usr = User::default();
|
||||
usr.full_name = Some("PokAdm".to_string());
|
||||
usr.admin = true;
|
||||
|
||||
Some(usr)*/
|
||||
}
|
||||
|
||||
pub async fn is_logged_in() -> bool {
|
||||
|
||||
+23
-4
@@ -1,15 +1,21 @@
|
||||
use std::error::Error;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use leptos::ServerFnError;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub enum AppError {
|
||||
HourParseError
|
||||
HourParseError,
|
||||
ServerError(String),
|
||||
FatalError(String)
|
||||
}
|
||||
|
||||
impl AppError {
|
||||
fn as_string(&self) -> String {
|
||||
//match self { AppError::HourParseError => {"Hour parse error"} }
|
||||
"Hours parse error".to_string()
|
||||
match self {
|
||||
AppError::HourParseError => {"Hour parse error".to_string()},
|
||||
AppError::ServerError(e) => {format!("Server error: {}", e)},
|
||||
AppError::FatalError(e) => {format!("Fatal error: {}", e)}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,4 +25,17 @@ impl Display for AppError {
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for AppError {}
|
||||
impl Error for AppError {}
|
||||
|
||||
impl From<ServerFnError> for AppError {
|
||||
fn from(value: ServerFnError) -> Self {
|
||||
AppError::ServerError(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
impl From<sqlx::Error> for AppError {
|
||||
fn from(value: sqlx::Error) -> Self {
|
||||
AppError::FatalError(value.to_string())
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ lazy_static! {
|
||||
("Search...", "Najít..."),
|
||||
("Close", "Zavřít"),
|
||||
("Save changes", "Uložit změny"),
|
||||
("Yes", "Ano"),
|
||||
("No", "Ne"),
|
||||
("Company info", "Organizace"),
|
||||
("Name cannot be empty", "Jméno nesmí být prázdné"),
|
||||
("Invalid old password", "Neplatné staré heslo"),
|
||||
@@ -25,6 +27,23 @@ lazy_static! {
|
||||
("Saturday", "Sobota"),
|
||||
("Sunday", "Neděle"),
|
||||
("Opening hours", "Otvírací hodiny"),
|
||||
("Username", "Uživatel"),
|
||||
("Password", "Heslo"),
|
||||
("Sign in", "Přihlásit"),
|
||||
("Create user", "Vytvořit uživatele"),
|
||||
("Delete user", "Smazat uživatele"),
|
||||
("Users", "Uživatelé"),
|
||||
("Full name", "Celé jméno"),
|
||||
("Actions", "Akce"),
|
||||
("Edit", "Upravit"),
|
||||
("Change password", "Změnit heslo"),
|
||||
("Delete", "Smazat"),
|
||||
("Properties", "Předměty"),
|
||||
("Name", "Jméno"),
|
||||
("Description", "Popis"),
|
||||
("Price", "Cena"),
|
||||
("Edit hours", "Upravit hodiny"),
|
||||
("Hours", "Hodiny"),
|
||||
])),
|
||||
("sk", HashMap::from( [
|
||||
("Dashboard", "Prehlad"),
|
||||
|
||||
+12
-1
@@ -1,3 +1,7 @@
|
||||
use log::error;
|
||||
use rezervator::backend::company::check_company;
|
||||
use rezervator::backend::user::create_admin;
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
@@ -34,7 +38,7 @@ async fn main() -> std::io::Result<()> {
|
||||
|
||||
let cfg_path = matches.opt_str("c").unwrap_or("config.toml".to_string());
|
||||
|
||||
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
|
||||
env_logger::Builder::from_env(Env::default().default_filter_or("debug")).init();
|
||||
info!("Starting server");
|
||||
|
||||
let conf = get_configuration(None).await.unwrap();
|
||||
@@ -49,6 +53,13 @@ async fn main() -> std::io::Result<()> {
|
||||
|
||||
migrate!().run(&pool).await.expect("could not run SQLx migrations");
|
||||
|
||||
if let Err(e) = create_admin(&pool).await {
|
||||
error!("Error while checking admin user: {:?}", e);
|
||||
}
|
||||
if let Err(e) = check_company(&pool).await {
|
||||
error!("Error while checking company: {:?}", e);
|
||||
}
|
||||
|
||||
HttpServer::new(move || {
|
||||
let leptos_options = &conf.leptos_options;
|
||||
let site_root = &leptos_options.site_root;
|
||||
|
||||
@@ -11,4 +11,7 @@ mod user_edit;
|
||||
mod user_delete;
|
||||
mod opening_hours;
|
||||
mod hours_edit;
|
||||
mod properties;
|
||||
mod property_edit;
|
||||
mod property_delete;
|
||||
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
use leptos::*;
|
||||
use leptos_use::use_media_query;
|
||||
use crate::backend::data::{ApiResponse, ResProperty};
|
||||
use crate::backend::property::get_properties;
|
||||
use crate::components::modal_box::DialogOpener;
|
||||
use crate::components::user_menu::MenuOpener;
|
||||
use crate::locales::trl;
|
||||
use crate::pages::property_delete::PropertyDelete;
|
||||
use crate::pages::property_edit::PropertyEdit;
|
||||
|
||||
#[component]
|
||||
pub fn properties() -> impl IntoView {
|
||||
let is_wide = use_media_query("(min-width: 500px)");
|
||||
let properties = create_rw_signal::<Vec<ResProperty>>(vec![]);
|
||||
let prop = create_rw_signal(ResProperty::default());
|
||||
let empty_prop = create_rw_signal(ResProperty::default());
|
||||
let create_form = DialogOpener::new();
|
||||
let edit_form = DialogOpener::new();
|
||||
let delete_dlg = DialogOpener::new();
|
||||
let props = create_blocking_resource(move || create_form.visible() || edit_form.visible() || delete_dlg.visible(), move |_| get_properties());
|
||||
|
||||
view! {
|
||||
<PropertyEdit property=prop.read_only() opener=edit_form edit=true/>
|
||||
<PropertyEdit property=empty_prop.read_only() opener=create_form edit=false/>
|
||||
<PropertyDelete property=prop.read_only() opener=delete_dlg/>
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><i class="bx bx-basket"></i>" "{trl("Properties")}</h5>
|
||||
<Transition fallback=move || view! {<p>{trl("Loading...")}</p> }>
|
||||
<table class="table card-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{trl("Name")}</th>
|
||||
{move || if is_wide.get() {view! {<th>{trl("Description")}</th>}}
|
||||
else {view! {<th></th>}} }
|
||||
<th>{trl("Price")}</th>
|
||||
<th>{trl("Actions")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{move || {
|
||||
props.get().map(|u| match u {
|
||||
Err(e) => {
|
||||
view! {<tbody class="table-border-bottom-0">
|
||||
<tr><td colspan=4>{trl("Something went wrong")}<br/>{e.to_string()}</td></tr></tbody>}}
|
||||
Ok(u) => {
|
||||
match u {
|
||||
ApiResponse::Data(p) => {
|
||||
properties.set(p.clone());
|
||||
view! {<tbody class="table-border-bottom-0">
|
||||
<For each=move || properties.get()
|
||||
key=|prop| prop.id()
|
||||
let:data>
|
||||
{move || {
|
||||
let menu = MenuOpener::new();
|
||||
let data = data.clone();
|
||||
let prop_for_edit = data.clone();
|
||||
let prop_for_delet = data.clone();
|
||||
view! {
|
||||
<tr>
|
||||
<td>{data.name.clone()}</td>
|
||||
{move || if is_wide.get() {view! {<td>{data.description.clone()}</td>}}
|
||||
else {view! {<td></td>}} }
|
||||
<td>{data.price.to_string()}</td>
|
||||
<td>
|
||||
<div class="dropdown">
|
||||
<button type="button" class="btn p-0 dropdown-toggle hide-arrow"
|
||||
on:click=move |_| menu.toggle()>
|
||||
<i class="bx bx-dots-vertical-rounded"></i>
|
||||
</button>
|
||||
<div class={move || if menu.visible() {"dropdown-menu show"} else {"dropdown-menu"} }
|
||||
style="position: absolute; insert: 0px 0px auto; margin: 0px; transform: translate3d(-160px, 0px, 0px);"
|
||||
on:mouseleave=move |_| menu.toggle()>
|
||||
<a class="dropdown-item" href="javascript:void(0);" on:click=move |_| {
|
||||
prop.set(prop_for_edit.clone());
|
||||
edit_form.show();
|
||||
}>
|
||||
<i class="bx bx-edit-alt me-1"></i> {trl("Edit")}</a>
|
||||
<a class="dropdown-item text-danger" href="javascript:void(0);" on:click=move |_| {
|
||||
prop.set(prop_for_delet.clone());
|
||||
delete_dlg.show();
|
||||
}>
|
||||
<i class="bx bx-trash me-1"></i> {trl("Delete")}</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>}
|
||||
}}
|
||||
</For>
|
||||
</tbody>
|
||||
}
|
||||
},
|
||||
ApiResponse::Error(s) => {
|
||||
view! {<tbody class="table-border-bottom-0">
|
||||
<tr><td colspan=4>{trl(&s)}</td></tr></tbody>}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</table>
|
||||
</Transition>
|
||||
<a href="#" class="card-link" on:click=move |_| {
|
||||
empty_prop.set(ResProperty::default());
|
||||
create_form.show();}>
|
||||
<i class="bx bx-plus-circle fs-4 lh-0"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
use leptos::*;
|
||||
use crate::backend::data::ResProperty;
|
||||
use crate::backend::property::DeleteProperty;
|
||||
use crate::components::data_form::QuestionDialog;
|
||||
use crate::components::modal_box::DialogOpener;
|
||||
|
||||
#[component]
|
||||
pub fn property_delete(property: ReadSignal<ResProperty>, opener: DialogOpener) -> impl IntoView {
|
||||
let del_property = create_server_action::<DeleteProperty>();
|
||||
|
||||
view! {
|
||||
<QuestionDialog opener=opener action=del_property title="Delete property">
|
||||
<input type="hidden" prop:value={move || property.get().id()} name="id"/>
|
||||
<div>"Are you sure you want to delete property "{move || property.get().name}"?"</div>
|
||||
</QuestionDialog>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
use leptos::*;
|
||||
use crate::backend::data::{ResProperty, SlotType};
|
||||
use crate::backend::property::{CreateProperty, EditProperty};
|
||||
use crate::components::data_form::DataForm;
|
||||
use crate::components::modal_box::DialogOpener;
|
||||
|
||||
#[component]
|
||||
fn form_inner(property: ReadSignal<ResProperty>) -> impl IntoView {
|
||||
let active_str = create_rw_signal(if property.get().active
|
||||
{ "true".to_string() } else { "false".to_string() });
|
||||
view! {
|
||||
<input type="hidden" prop:value={move || property.get().id()} name="property[id]"/>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<label for="name" class="form-label">"Name"</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
class="form-control"
|
||||
placeholder="Enter name"
|
||||
prop:value={move || property.get().name}
|
||||
name="property[name]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<label for="description" class="form-label">"Description"</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
class="form-control"
|
||||
placeholder="Enter description"
|
||||
prop:value={move || property.get().description}
|
||||
name="property[description]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<label for="price" class="form-label">"Price"</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
class="form-control"
|
||||
placeholder=""
|
||||
prop:value={move || property.get().price.to_string()}
|
||||
name="property[price]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<label for="slot" class="form-label">Time slot</label>
|
||||
<select id="slot" name="property[slot]" class="form-select">
|
||||
<option value="Quarter" selected=move || property.get().slot == SlotType::Quarter>"Quarter an hour"</option>
|
||||
<option value="Half" selected=move || property.get().slot == SlotType::Half>"Half an hour"</option>
|
||||
<option value="Hour" selected=move || property.get().slot == SlotType::Hour>"Hour"</option>
|
||||
<option value="Day" selected=move || property.get().slot == SlotType::Day>"Day"</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{move || {
|
||||
if property.get().id() != 0 {
|
||||
view! {
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="active"
|
||||
class="form-check-input"
|
||||
prop:checked={move || property.get().active}
|
||||
on:click=move |_| active_str.set(if active_str.get() == "true".to_string()
|
||||
{ "false".to_string() } else { "true".to_string() })
|
||||
/>
|
||||
<label for="active" class="form-label">"Active"</label>
|
||||
<input type="hidden" prop:value=active_str name="property[active]"/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
} else {
|
||||
view! {<div></div>}
|
||||
}
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn property_edit(property: ReadSignal<ResProperty>, edit: bool, opener: DialogOpener) -> impl IntoView {
|
||||
let action_create = create_server_action::<CreateProperty>();
|
||||
let action_edit = create_server_action::<EditProperty>();
|
||||
|
||||
view! {
|
||||
{move ||
|
||||
if edit {
|
||||
view! {<DataForm opener=opener action=action_edit title="Edit property">
|
||||
<FormInner property=property/>
|
||||
</DataForm>}}
|
||||
else {
|
||||
view! {<DataForm opener=opener action=action_create title="Create property">
|
||||
<FormInner property=property/>
|
||||
</DataForm>}}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ use crate::locales::trl;
|
||||
use crate::pages::company_info::CompanyInfo;
|
||||
use crate::pages::opening_hours::OpeningHours;
|
||||
use crate::pages::users::Users;
|
||||
use crate::pages::properties::Properties;
|
||||
|
||||
#[component]
|
||||
pub fn Settings() -> impl IntoView {
|
||||
@@ -21,7 +22,7 @@ pub fn Settings() -> impl IntoView {
|
||||
<OpeningHours/>
|
||||
</div>
|
||||
<div class="col-md">
|
||||
|
||||
<Properties/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user