Implemented settings for properties of reservation. Fixed bugs on empty database.
parent
d7e33e4342
commit
55ca93406a
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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>}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue