Finished Unit edit

This commit is contained in:
Franklin 2023-04-26 06:48:36 -04:00
parent 65395552e0
commit 7177823b14
16 changed files with 377 additions and 127 deletions

View File

@ -1,15 +1,20 @@
use std::collections::HashSet; use std::collections::HashSet;
use jl_types::{ use jl_types::{
domain::{agent::Agent, contact::Contact, count::Count, location::Location, project::Project, unit::Unit}, domain::{
agent::Agent, contact::Contact, count::Count, location::Location, project::Project,
unit::Unit,
},
dto::{ dto::{
filters::Filter, filters::Filter,
item::Item, item::Item,
listing::Listing, listing::Listing,
payloads::{ payloads::{
agent::{NewAgentPayload, UpdateAgentPayload},
contact::ContactPayload, contact::ContactPayload,
location::NewLocationPayload, location::NewLocationPayload,
project::{NewProjectPayload, UpdateProjectPayload}, unit::UpdateUnitPayload, agent::{UpdateAgentPayload, NewAgentPayload}, project::{NewProjectPayload, UpdateProjectPayload},
unit::{UpdateUnitPayload, NewUnitPayload},
}, },
project_card::ProjectCardDto, project_card::ProjectCardDto,
}, },
@ -213,6 +218,20 @@ pub async fn create_new_agent(agent: NewAgentPayload) -> Result<Agent, err::Erro
) )
.await .await
} }
pub async fn create_new_unit(unit: NewUnitPayload) -> Result<Unit, err::Error> {
perform_request_without_client(
BASE_URL.into(),
Method::POST,
format!("admin/unit"),
Some(unit),
200,
Vec::new(),
None,
)
.await
}
pub async fn create_new_project(project: NewProjectPayload) -> Result<Project, err::Error> { pub async fn create_new_project(project: NewProjectPayload) -> Result<Project, err::Error> {
perform_request_without_client( perform_request_without_client(
BASE_URL.into(), BASE_URL.into(),

View File

@ -3,8 +3,9 @@ use yew::prelude::*;
use yew_router::prelude::use_navigator; use yew_router::prelude::use_navigator;
use crate::{ use crate::{
api::backend::delete_agent,
pages::admin::edit::{EditItem, EditType}, pages::admin::edit::{EditItem, EditType},
routes::main_router::Route, api::backend::delete_agent, routes::main_router::Route,
}; };
//TODO: Add admin tag //TODO: Add admin tag

View File

@ -4,8 +4,9 @@ use yew::prelude::*;
use yew_router::prelude::use_navigator; use yew_router::prelude::use_navigator;
use crate::{ use crate::{
api::backend::delete_unit,
pages::admin::edit::{EditItem, EditType}, pages::admin::edit::{EditItem, EditType},
routes::main_router::Route, api::backend::delete_unit, routes::main_router::Route,
}; };
#[function_component(AdminUnit)] #[function_component(AdminUnit)]
@ -20,7 +21,7 @@ pub fn admin_unit(props: &AdminUnitProps) -> Html {
format!("{price_separated}.00") format!("{price_separated}.00")
} }
}); });
let delete_unit = { let delete_unit = {
let is_attempting_delete = is_attempting_delete.clone(); let is_attempting_delete = is_attempting_delete.clone();
let deletecb = props.deletecb.clone(); let deletecb = props.deletecb.clone();
@ -89,5 +90,5 @@ pub fn admin_unit(props: &AdminUnitProps) -> Html {
pub struct AdminUnitProps { pub struct AdminUnitProps {
pub unit: Unit, pub unit: Unit,
pub index: usize, pub index: usize,
pub deletecb: Callback<usize> pub deletecb: Callback<usize>,
} }

View File

@ -15,8 +15,6 @@ pub struct DropDownProps<T: Display + std::cmp::PartialEq + Clone> {
pub onchange: Option<Callback<Option<T>>>, pub onchange: Option<Callback<Option<T>>>,
} }
#[function_component(DropDown)] #[function_component(DropDown)]
pub fn dropdown<T: Display + std::cmp::PartialEq + Clone + 'static>( pub fn dropdown<T: Display + std::cmp::PartialEq + Clone + 'static>(
props: &DropDownProps<T>, props: &DropDownProps<T>,
@ -33,19 +31,19 @@ pub fn dropdown<T: Display + std::cmp::PartialEq + Clone + 'static>(
.collect(); .collect();
options.insert(0, None); options.insert(0, None);
options options
}, },
selection_changed: match props.onchange.clone() { selection_changed: match props.onchange.clone() {
Some(cb) => cb, Some(cb) => cb,
None => Callback::from(|_| {}) None => Callback::from(|_| {}),
}, },
class_css: Some("admin-dropdown".into()), class_css: Some("admin-dropdown".into()),
none_str: props.unpicked_text.clone() none_str: props.unpicked_text.clone(),
}, },
); );
//if (*props.selected).is_none() {selection_changed_cb.emit( OptionWrapper::new((*props.selected).clone()));} //if (*props.selected).is_none() {selection_changed_cb.emit( OptionWrapper::new((*props.selected).clone()));}
html! { html! {
{drop_down} {drop_down}
} }
} }

View File

@ -1,4 +1,4 @@
use jl_types::domain::media::{Media, MediaList}; use jl_types::{domain::media::{Media, MediaList}, dto::item::Item};
use js_sys::Uint8Array; use js_sys::Uint8Array;
use wasm_bindgen_futures::JsFuture; use wasm_bindgen_futures::JsFuture;
use yew::prelude::*; use yew::prelude::*;
@ -12,7 +12,11 @@ use crate::{
pub fn media_picker(props: &MediaPickerProps) -> Html { pub fn media_picker(props: &MediaPickerProps) -> Html {
html! { html! {
<> <>
<div class={{"textfield-label-required"}}>{"Media del proyecto"}</div> <div class={{"textfield-label-required"}}>{format!("Media {}", match props.item {
Item::Project => "del Proyecto",
Item::Unit => "de la Unidad",
Item::Agent => "del Agente"
})}</div>
<div class={"mediapicker-container"}> <div class={"mediapicker-container"}>
<MediaListRendered medialist={props.value.clone()} onchange={props.onchange.clone()} item={props.item.clone()}/> <MediaListRendered medialist={props.value.clone()} onchange={props.onchange.clone()} item={props.item.clone()}/>
</div> </div>

View File

@ -12,7 +12,7 @@ pub mod media_picker;
pub mod media_slideshow; pub mod media_slideshow;
pub mod nav_bar; pub mod nav_bar;
pub mod new_widget; pub mod new_widget;
pub mod number_textfield;
pub mod project_card; pub mod project_card;
pub mod textfield;
pub mod single_media_picker; pub mod single_media_picker;
pub mod number_textfield; pub mod textfield;

View File

@ -1,8 +1,7 @@
use std::{fmt::Display, str::FromStr};
use wasm_bindgen::{JsCast, UnwrapThrowExt}; use wasm_bindgen::{JsCast, UnwrapThrowExt};
use web_sys::{HtmlInputElement}; use web_sys::HtmlInputElement;
use yew::prelude::*; use yew::prelude::*;
use std::{str::FromStr, fmt::{Display}};
#[derive(Properties, PartialEq, Clone)] #[derive(Properties, PartialEq, Clone)]
pub struct NumberTextFieldProps<T: PartialEq + Clone + Display + Default + FromStr> { pub struct NumberTextFieldProps<T: PartialEq + Clone + Display + Default + FromStr> {
@ -14,23 +13,26 @@ pub struct NumberTextFieldProps<T: PartialEq + Clone + Display + Default + FromS
} }
#[function_component(NumberTextField)] #[function_component(NumberTextField)]
pub fn number_textfield<T: PartialEq + Clone + Display + Default + FromStr + 'static>(props: &NumberTextFieldProps<T>) -> Html { pub fn number_textfield<T: PartialEq + Clone + Display + Default + FromStr + 'static>(
props: &NumberTextFieldProps<T>,
) -> Html {
let on_input_changed = { let on_input_changed = {
let handle = props.value.clone(); let handle = props.value.clone();
let onchange = props.onchange.clone(); let onchange = props.onchange.clone();
Callback::from(move |e: InputEvent| { Callback::from(move |e: InputEvent| {
let value = match get_number_value_from_input_event::<T>(e) { let value = match get_number_value_from_input_event::<T>(e) {
Ok(float) => float, Ok(float) => float,
Err(_) => { Err(_) => {
log::error!("Error ocurred attempting to parse number on input type number. This only happens in firefox browsers."); log::error!("Error ocurred attempting to parse number on input type number. This only happens in firefox browsers.");
T::default() T::default()
}}; }
match onchange.clone() { };
Some(onchange) => onchange.emit(value.clone()), match onchange.clone() {
None => {} Some(onchange) => onchange.emit(value.clone()),
}; None => {}
handle.set(value); };
}) handle.set(value);
})
}; };
html! { html! {
<div class={"textfield-container"}> <div class={"textfield-container"}>
@ -48,4 +50,4 @@ pub fn get_number_value_from_input_event<T: FromStr>(e: InputEvent) -> Result<T,
Ok(t) => Ok(t), Ok(t) => Ok(t),
Err(_) => Err(()), Err(_) => Err(()),
} }
} }

View File

@ -2,10 +2,7 @@ use js_sys::Uint8Array;
use wasm_bindgen_futures::JsFuture; use wasm_bindgen_futures::JsFuture;
use yew::prelude::*; use yew::prelude::*;
use crate::{ use crate::{api, components::textfield::get_files_from_input_event};
api,
components::textfield::{get_files_from_input_event},
};
#[derive(PartialEq, Properties, Clone)] #[derive(PartialEq, Properties, Clone)]
pub struct SingleMediaPickerProps { pub struct SingleMediaPickerProps {
@ -102,9 +99,8 @@ pub fn single_media_picker(props: &SingleMediaPickerProps) -> Html {
} }
} }
} }
</div> </div>
</> </>
} }
} }

View File

@ -5,13 +5,9 @@ use yew::prelude::*;
use yew_router::prelude::use_navigator; use yew_router::prelude::use_navigator;
use crate::{ use crate::{
api::backend::{ api::backend::{get_agent_with_id, get_project_listing},
get_project_listing, get_agent_with_id, get_unit_with_id, components::admin_nav_bar::AdminNavigationBar,
}, pages::admin::fields::{agent::AgentFields, project::ProjectFields, unit::UnitFields},
components::{
admin_nav_bar::AdminNavigationBar,
},
pages::admin::{fields::{project::ProjectFields, unit::UnitFields, agent::AgentFields}},
}; };
/// All of the editing actions of the admin panel will lead to here. This should take an id of anything. A unit, a project, an agent. And its corresponding ID. /// All of the editing actions of the admin panel will lead to here. This should take an id of anything. A unit, a project, an agent. And its corresponding ID.
@ -20,11 +16,9 @@ pub fn edit_page(props: &AdminEditPageProps) -> Html {
let _navigator = use_navigator().unwrap(); let _navigator = use_navigator().unwrap();
let listing = use_state(|| None); let listing = use_state(|| None);
let agent = use_state(|| None); let agent = use_state(|| None);
let unit = use_state(|| None);
use_state(|| { use_state(|| {
let listing = listing.clone(); let listing = listing.clone();
let agent = agent.clone(); let agent = agent.clone();
let unit = unit.clone();
match props.edit_item { match props.edit_item {
EditItem::Agent => { EditItem::Agent => {
match props.edit_type { match props.edit_type {
@ -61,17 +55,7 @@ pub fn edit_page(props: &AdminEditPageProps) -> Html {
EditItem::Unit(_) => { EditItem::Unit(_) => {
match props.edit_type { match props.edit_type {
EditType::New => {} EditType::New => {}
EditType::Existing(uid) => { EditType::Existing(_) => {}
wasm_bindgen_futures::spawn_local(async move {
let unit_result = get_unit_with_id(&uid).await;
match unit_result {
Ok(unit_persisted) => {
unit.set(Some(unit_persisted));
}
Err(error) => log::error!("Error loading unit: {error}"),
};
});
}
}; };
} }
} }
@ -97,7 +81,7 @@ pub fn edit_page(props: &AdminEditPageProps) -> Html {
match props.edit_item { match props.edit_item {
EditItem::Agent => { html! { <AgentFields agent={(*agent).clone()} edittype={props.edit_type.clone()} /> } }, EditItem::Agent => { html! { <AgentFields agent={(*agent).clone()} edittype={props.edit_type.clone()} /> } },
EditItem::Project => { html! { <ProjectFields listing={(*listing).clone()} edittype={props.edit_type.clone()}/> } }, EditItem::Project => { html! { <ProjectFields listing={(*listing).clone()} edittype={props.edit_type.clone()}/> } },
EditItem::Unit(project_id) => { html! { <UnitFields edittype={props.edit_type.clone()} unit={(*unit).clone()} projectid={project_id}/> } }, EditItem::Unit(project_id) => { html! { <UnitFields edittype={props.edit_type.clone()} projectid={project_id}/> } },
} }
} }
</div> </div>

View File

@ -1,8 +1,18 @@
use jl_types::{domain::{agent::Agent, credential::CredentialType}, dto::payloads::agent::{NewAgentPayload, UpdateAgentPayload}}; use jl_types::{
domain::{agent::Agent, credential::CredentialType},
dto::payloads::agent::{NewAgentPayload, UpdateAgentPayload},
};
use yew::prelude::*; use yew::prelude::*;
use yew_router::prelude::use_navigator; use yew_router::prelude::use_navigator;
use crate::{pages::admin::edit::{EditType}, components::{textfield::TextField, dropdown::DropDown, single_media_picker::SingleMediaPicker}, api::backend::{update_agent, create_new_agent}, routes::main_router::Route, }; use crate::{
api::backend::{create_new_agent, update_agent},
components::{
dropdown::DropDown, single_media_picker::SingleMediaPicker, textfield::TextField,
},
pages::admin::edit::EditType,
routes::main_router::Route,
};
#[derive(Properties, PartialEq, Clone)] #[derive(Properties, PartialEq, Clone)]
pub struct AgentFieldsProps { pub struct AgentFieldsProps {
@ -47,14 +57,12 @@ pub fn agent_fields(props: &AgentFieldsProps) -> Html {
None => String::new(), None => String::new(),
}); });
profile_picture_url_handle.set(match agent_opt.clone() { profile_picture_url_handle.set(match agent_opt.clone() {
Some(agent) => { Some(agent) => agent.profile_picture_url,
agent.profile_picture_url
},
None => String::new(), None => String::new(),
}); });
credential_type.set(match agent_opt.clone() { credential_type.set(match agent_opt.clone() {
Some(agent) => Some(agent.credential_type), Some(agent) => Some(agent.credential_type),
None => None None => None,
}); });
credential_handle.set(match agent_opt.clone() { credential_handle.set(match agent_opt.clone() {
Some(agent) => agent.credential, Some(agent) => agent.credential,
@ -96,48 +104,48 @@ pub fn agent_fields(props: &AgentFieldsProps) -> Html {
let agent_name = (*agent_name_handle).clone(); let agent_name = (*agent_name_handle).clone();
let profile_picture_url = (*profile_picture_url_handle).clone(); let profile_picture_url = (*profile_picture_url_handle).clone();
let credential = (*credential_handle).clone(); let credential = (*credential_handle).clone();
let credential_type = if let Some(credential_type) = (*credential_type_handle).clone(){ let credential_type =
credential_type if let Some(credential_type) = (*credential_type_handle).clone() {
} else { credential_type
log::error!("Missing field credential_type"); } else {
return; log::error!("Missing field credential_type");
}; return;
};
match edit_type { match edit_type {
EditType::New => { EditType::New => {
let agent = NewAgentPayload { let agent = NewAgentPayload {
credential, credential,
credential_type, credential_type,
full_name: agent_name, full_name: agent_name,
profile_picture_url profile_picture_url,
}; };
match create_new_agent(agent).await { match create_new_agent(agent).await {
Ok(_) => navigator.push(&Route::AdminAgents), Ok(_) => navigator.push(&Route::AdminAgents),
Err(error) => log::error!("Error updating agent: {error}") Err(error) => log::error!("Error updating agent: {error}"),
}; };
}, }
EditType::Existing(id) => { EditType::Existing(id) => {
let agent = UpdateAgentPayload { let agent = UpdateAgentPayload {
id, id,
credential: Some(credential), credential: Some(credential),
credential_type: Some(credential_type), credential_type: Some(credential_type),
full_name: Some(agent_name), full_name: Some(agent_name),
profile_picture_url: Some(profile_picture_url) profile_picture_url: Some(profile_picture_url),
}; };
match update_agent(agent).await { match update_agent(agent).await {
Ok(_) => navigator.push(&Route::AdminAgents), Ok(_) => navigator.push(&Route::AdminAgents),
Err(error) => log::error!("Error updating agent: {error}") Err(error) => log::error!("Error updating agent: {error}"),
}; };
} }
}; };
}); });
}) })
}; };
html! { html! {
<> <>
<SingleMediaPicker value={profile_picture_url_handle} onchange={ontype_cb.clone()} item={jl_types::dto::item::Item::Agent}/> <SingleMediaPicker value={profile_picture_url_handle} item={jl_types::dto::item::Item::Agent} onchange={ontype_cb.clone()} />
<TextField label={"Nombre Completo"} value={agent_name_handle} required={true} onchange={ontype_cb.clone()}/> <TextField label={"Nombre Completo"} value={agent_name_handle} required={true} onchange={ontype_cb.clone()}/>
{if (*credential_type).clone().is_none() { {if (*credential_type).clone().is_none() {
match props.edittype.clone() { match props.edittype.clone() {
EditType::New => html! { EditType::New => html! {
@ -179,7 +187,7 @@ pub fn agent_fields(props: &AgentFieldsProps) -> Html {
<div class={"admin-edit-submit-button"} onclick={update_button_onclick}> <div class={"admin-edit-submit-button"} onclick={update_button_onclick}>
{"Actualizar"} {"Actualizar"}
</div> </div>
</> </>
} }
} }

View File

@ -1,3 +1,3 @@
pub mod agent;
pub mod project; pub mod project;
pub mod unit; pub mod unit;
pub mod agent;

View File

@ -1,10 +1,39 @@
use chrono::NaiveTime; use chrono::NaiveTime;
use jl_types::{dto::{listing::Listing, payloads::{location::NewLocationPayload, project::{UpdateProjectPayload, NewProjectPayload}}}, domain::{agent::Agent, media::MediaList, unit::Unit, project_condition::ProjectCondition, project_type::ProjectType, project_state::ProjectState}}; use jl_types::{
domain::{
agent::Agent, media::MediaList, project_condition::ProjectCondition,
project_state::ProjectState, project_type::ProjectType, unit::Unit,
},
dto::{
listing::Listing,
payloads::{
location::NewLocationPayload,
project::{NewProjectPayload, UpdateProjectPayload},
},
},
};
use std::str::FromStr;
use yew::prelude::*; use yew::prelude::*;
use yew_router::prelude::use_navigator; use yew_router::prelude::use_navigator;
use std::str::FromStr;
use crate::{pages::admin::{edit::{EditType, EditItem}, units::AdminUnits}, api::backend::{get_all_agents, get_location_with_city_and_district, create_location, create_new_project, update_project}, routes::main_router::Route, components::{new_widget::NewThingWidget, dropdown::DropDown, media_picker::MediaPicker, textfield::{TextField, TextFieldType}, datepicker::DatePicker}}; use crate::{
api::backend::{
create_location, create_new_project, get_all_agents, get_location_with_city_and_district,
update_project,
},
components::{
datepicker::DatePicker,
dropdown::DropDown,
media_picker::MediaPicker,
new_widget::NewThingWidget,
textfield::{TextField, TextFieldType},
},
pages::admin::{
edit::{EditItem, EditType},
units::AdminUnits,
},
routes::main_router::Route,
};
#[derive(Properties, PartialEq, Clone)] #[derive(Properties, PartialEq, Clone)]
pub struct ProjectFieldsProps { pub struct ProjectFieldsProps {
@ -93,9 +122,7 @@ pub fn generate_fields_for_project(props: &ProjectFieldsProps) -> Html {
}); });
project_state.set(match listing_opt.clone() { project_state.set(match listing_opt.clone() {
Some(listing) => Some(listing.project.project_state), Some(listing) => Some(listing.project.project_state),
None => { None => None,
None
},
}); });
project_condition.set(match listing_opt.clone() { project_condition.set(match listing_opt.clone() {
Some(listing) => Some(listing.project.project_condition), Some(listing) => Some(listing.project.project_condition),
@ -427,4 +454,4 @@ pub fn generate_fields_for_project(props: &ProjectFieldsProps) -> Html {
<AdminUnits units={units} onchange={ontype_cb.clone()}/> <AdminUnits units={units} onchange={ontype_cb.clone()}/>
</> </>
} }
} }

View File

@ -1,36 +1,253 @@
use jl_types::domain::{unit::Unit, unit_type::UnitType}; use jl_types::{domain::{media::MediaList, unit_type::UnitType}, dto::payloads::unit::{NewUnitPayload, UpdateUnitPayload}};
use uuid::Uuid; use uuid::Uuid;
use yew::prelude::*; use yew::prelude::*;
use yew_router::prelude::use_navigator; use yew_router::prelude::use_navigator;
use crate::{pages::admin::edit::EditType, components::number_textfield::NumberTextField}; use crate::{
components::{dropdown::DropDown, number_textfield::NumberTextField, media_picker::MediaPicker, textfield::{TextField, TextFieldType}},
pages::admin::edit::EditType, api::backend::{get_unit_with_id, create_new_unit, update_unit}, routes::main_router::Route,
};
#[derive(Properties, PartialEq, Clone)] #[derive(Properties, PartialEq, Clone)]
pub struct UnitFieldsProps { pub struct UnitFieldsProps {
pub unit: Option<Unit>,
pub edittype: EditType, pub edittype: EditType,
pub projectid: Option<Uuid>, pub projectid: Uuid,
} }
#[function_component(UnitFields)] #[function_component(UnitFields)]
pub fn unit_fields(props: &UnitFieldsProps) -> Html { pub fn unit_fields(props: &UnitFieldsProps) -> Html {
let unit_opt = use_state(|| None);
{
let unit_opt = unit_opt.clone();
use_state(move || {
if let EditType::Existing(id) = props.edittype.clone() {
wasm_bindgen_futures::spawn_local(async move {
let unit_result = get_unit_with_id(&id).await;
match unit_result {
Ok(unit_persisted) => {
unit_opt.set(Some(unit_persisted));
}
Err(error) => log::error!("Error loading unit: {error}"),
};
});
}
});
}
let navigator = use_navigator().unwrap(); let navigator = use_navigator().unwrap();
let user_typed = use_state(|| false); let user_typed = use_state(|| false);
let edit_type = props.edittype.clone();
let project_id = props.projectid.clone();
let ontype_cb = {
let user_typed = user_typed.clone();
Callback::from(move |_: String| {
user_typed.set(true);
})
};
let onselect_cb = {
let user_typed = user_typed.clone();
Callback::from(move |_| {
user_typed.set(true);
})
};
let onchange_number_cb = {
let user_typed = user_typed.clone();
Callback::from(move |_| {
user_typed.set(true);
})
};
let onchange_number_cb_2 = {
let user_typed = user_typed.clone();
Callback::from(move |_| {
user_typed.set(true);
})
};
let onchange_number_cb_3 = {
let user_typed = user_typed.clone();
Callback::from(move |_| {
user_typed.set(true);
})
};
// Fields // Fields
let price_usd = use_state(|| 0.0); let price_usd = use_state_eq(|| 0.0);
let unit_type_handle: UseStateHandle<Option<UnitType>> = use_state(|| None); let unit_type_handle: UseStateHandle<Option<UnitType>> = use_state_eq(|| None);
let rooms_handle: UseStateHandle<i16> = use_state(|| 0); let rooms_handle: UseStateHandle<i16> = use_state_eq(|| 0);
let bathrooms_handle: UseStateHandle<i16> = use_state(|| 0); let bathrooms_handle: UseStateHandle<i16> = use_state_eq(|| 0);
let area_handle: UseStateHandle<f32> = use_state(|| 0.0); let area_handle: UseStateHandle<f32> = use_state_eq(|| 0.0);
let a = 0.0; let media_handle: UseStateHandle<MediaList> = use_state_eq(|| MediaList {
let b = a as i32; media_list: Vec::new(),
});
let unit_admin_tag_handle: UseStateHandle<String> = use_state_eq(|| String::new());
let unit_description_handle = use_state_eq(|| String::new());
if !*user_typed {
price_usd.set(match (*unit_opt).clone() {
Some(unit) => unit.price_usd,
None => 0.0,
});
unit_type_handle.set(match (*unit_opt).clone() {
Some(unit) => Some(unit.unit_type),
None => None
});
media_handle.set(match (*unit_opt).clone() {
Some(unit) => unit.media,
None => MediaList {
media_list: Vec::new(),
},
});
rooms_handle.set(match (*unit_opt).clone() {
Some(unit) => unit.rooms,
None => 0
});
bathrooms_handle.set(match (*unit_opt).clone() {
Some(unit) => unit.bathrooms,
None => 0
});
area_handle.set(match (*unit_opt).clone() {
Some(unit) => unit.area,
None => 0.0
});
unit_description_handle.set(match (*unit_opt).clone() {
Some(unit) => unit.description,
None => String::new(),
});
unit_admin_tag_handle.set(match (*unit_opt).clone() {
Some(unit) => match unit.admin_tag {
Some(admin_tag) => admin_tag,
None => String::new()
},
None => String::new(),
});
}
let update_button_onclick = {
let navigator = navigator.clone();
let edit_type = edit_type.clone();
let project_id = project_id.clone();
let price_usd = price_usd.clone();
let unit_type_handle: UseStateHandle<Option<UnitType>> = unit_type_handle.clone();
let rooms_handle: UseStateHandle<i16> = rooms_handle.clone();
let bathrooms_handle: UseStateHandle<i16> = bathrooms_handle.clone();
let area_handle: UseStateHandle<f32> = area_handle.clone();
let media_handle: UseStateHandle<MediaList> = media_handle.clone();
let unit_admin_tag_handle: UseStateHandle<String> = unit_admin_tag_handle.clone();
let unit_description_handle = unit_description_handle.clone();
Callback::from(move |_: MouseEvent| {
let navigator = navigator.clone();
let edit_type = edit_type.clone();
let project_id = project_id.clone();
let price_usd = price_usd.clone();
let unit_type_handle: UseStateHandle<Option<UnitType>> = unit_type_handle.clone();
let rooms_handle: UseStateHandle<i16> = rooms_handle.clone();
let bathrooms_handle: UseStateHandle<i16> = bathrooms_handle.clone();
let area_handle: UseStateHandle<f32> = area_handle.clone();
let media_handle: UseStateHandle<MediaList> = media_handle.clone();
let unit_admin_tag_handle: UseStateHandle<String> = unit_admin_tag_handle.clone();
let unit_description_handle = unit_description_handle.clone();
wasm_bindgen_futures::spawn_local(async move {
let price_usd = (*price_usd).clone();
let unit_type = match (*unit_type_handle).clone() {
Some(unit_type) => unit_type,
None => {
log::error!("Missing field unit_type");
return;
}
};
let rooms = (*rooms_handle).clone();
let bathrooms = (*bathrooms_handle).clone();
let area = (*area_handle).clone();
let media = (*media_handle).clone();
let unit_admin_tag_val = (*unit_admin_tag_handle).clone();
let admin_tag = if unit_admin_tag_val.is_empty() { None } else { Some(unit_admin_tag_val) };
let description = (*unit_description_handle).clone();
match edit_type {
EditType::New => {
let payload = NewUnitPayload {
project_id,
price_usd,
unit_type,
rooms,
bathrooms,
area,
admin_tag,
media,
description
};
match create_new_unit(payload).await {
Ok(_) => navigator.clone().push(&Route::AdminProjects),
Err(error) => log::error!("Error creating new unit: {error}"),
};
},
EditType::Existing(id) => {
let payload = UpdateUnitPayload {
id,
price_usd: Some(price_usd),
unit_type: Some(unit_type),
rooms: Some(rooms),
bathrooms: Some(bathrooms),
area: Some(area),
admin_tag: Some(admin_tag),
media: Some(media),
description: Some(description)
};
match update_unit(payload).await {
Ok(_) => navigator.clone().push(&Route::AdminProjects),
Err(error) => log::error!("Error creating new unit: {error}"),
};
}
};
});
})
};
html! { html! {
<> <>
<NumberTextField<f64> label={String::from("Precio en USD")} required={true} value={price_usd.clone()}/> <NumberTextField<f64> label={String::from("Precio en USD")} required={true} value={price_usd.clone()} onchange={onchange_number_cb}/>
<NumberTextField<f32> label={String::from("Area en m^2")} required={true} value={area_handle.clone()}/> <NumberTextField<f32> label={String::from("Area en m^2")} required={true} value={area_handle.clone()} onchange={onchange_number_cb_2}/>
<NumberTextField<i16> label={String::from("Cant. de habitaciones")} required={true} value={rooms_handle.clone()}/> <NumberTextField<i16> label={String::from("Cant. de habitaciones")} required={true} value={rooms_handle.clone()} onchange={onchange_number_cb_3.clone()}/>
<NumberTextField<i16> label={String::from("Cant. de baños")} required={true} value={bathrooms_handle.clone()}/> <NumberTextField<i16> label={String::from("Cant. de baños")} required={true} value={bathrooms_handle.clone()} onchange={onchange_number_cb_3}/>
<MediaPicker value={media_handle} onchange={ontype_cb.clone()} item={jl_types::dto::item::Item::Unit}/>
// Unit type dropdown
{if (*unit_type_handle).clone().is_none() {
match props.edittype.clone() {
EditType::New => html! {
<div class={"textfield-container"}>
<div class={"textfield-label-required"}>{"Tipo de Unidad"}</div>
<DropDown<UnitType> selected={unit_type_handle} options={vec![UnitType::ForSale, UnitType::NotForSale] } onchange={onselect_cb} />
</div>
},
EditType::Existing(_) => if *user_typed {
html! {
<div class={"textfield-container"}>
<div class={"textfield-label-required"}>{"Tipo de Unidad"}</div>
<DropDown<UnitType> selected={unit_type_handle} options={vec![UnitType::ForSale, UnitType::NotForSale] } onchange={onselect_cb} />
</div>
}
} else {
html! {}
}
}
} else {
html! {
<div class={"textfield-container"}>
<div class={"textfield-label-required"}>{"Tipo de Unidad"}</div>
<DropDown<UnitType> selected={unit_type_handle} options={vec![UnitType::ForSale, UnitType::NotForSale] } onchange={onselect_cb} />
</div>
}
}
}
<TextField label={"Descripción"} value={unit_description_handle} required={false} onchange={ontype_cb.clone()} fieldtype={TextFieldType::TextArea} />
<TextField label={"Comentario interno"} value={unit_admin_tag_handle} required={false} onchange={ontype_cb.clone()} />
<div class={"admin-edit-submit-button"} onclick={update_button_onclick}>
{"Actualizar"}
</div>
</> </>
} }
} }

View File

@ -1,8 +1,8 @@
pub mod agents; pub mod agents;
pub mod contacts; pub mod contacts;
pub mod edit; pub mod edit;
pub mod fields;
pub mod login; pub mod login;
pub mod projects; pub mod projects;
pub mod start; pub mod start;
pub mod units; pub mod units;
pub mod fields;

View File

@ -5,7 +5,6 @@ use crate::components::admin_unit::AdminUnit;
#[function_component(AdminUnits)] #[function_component(AdminUnits)]
pub fn admin_units(props: &AdminUnitProps) -> Html { pub fn admin_units(props: &AdminUnitProps) -> Html {
let units_handle = props.units.clone(); let units_handle = props.units.clone();
html! { html! {
<div class={"admin-start-container"} style={"min-height: 10vh; margin-top: 10vh;"}> <div class={"admin-start-container"} style={"min-height: 10vh; margin-top: 10vh;"}>
@ -38,5 +37,5 @@ pub fn admin_units(props: &AdminUnitProps) -> Html {
#[derive(PartialEq, Properties, Clone)] #[derive(PartialEq, Properties, Clone)]
pub struct AdminUnitProps { pub struct AdminUnitProps {
pub units: UseStateHandle<Vec<Unit>>, pub units: UseStateHandle<Vec<Unit>>,
pub onchange: Option<Callback<String>>, pub onchange: Option<Callback<String>>,
} }

View File

@ -10,7 +10,9 @@ use crate::{
api::backend::{ api::backend::{
get_all_cities, get_all_districts_in_city, get_all_projects_with_filters_paged, get_all_cities, get_all_districts_in_city, get_all_projects_with_filters_paged,
}, },
components::{footer::PageFooter, nav_bar::NavigationBar, project_card::ProjectCard, dropdown::DropDown}, components::{
dropdown::DropDown, footer::PageFooter, nav_bar::NavigationBar, project_card::ProjectCard,
},
}; };
#[function_component(SearchPage)] #[function_component(SearchPage)]
@ -31,10 +33,7 @@ pub fn search_page() -> Html {
wasm_bindgen_futures::spawn_local(async move { wasm_bindgen_futures::spawn_local(async move {
match get_all_cities().await { match get_all_cities().await {
Ok(cities) => { Ok(cities) => {
let cities: Vec<String> = cities let cities: Vec<String> = cities.into_iter().map(|location| location).collect();
.into_iter()
.map(|location| location)
.collect();
cities_handle.set(cities); cities_handle.set(cities);
} }
Err(error) => log::error!("Error in loading cities: {error}"), Err(error) => log::error!("Error in loading cities: {error}"),
@ -55,19 +54,14 @@ pub fn search_page() -> Html {
}); });
// Dropdown // Dropdown
let project_type_filter: UseStateHandle<Option<ProjectType>> = let project_type_filter: UseStateHandle<Option<ProjectType>> = use_state(|| None);
use_state(|| None);
// Dropdown // Dropdown
let project_condition_filter: UseStateHandle<Option<ProjectCondition>> = let project_condition_filter: UseStateHandle<Option<ProjectCondition>> = use_state(|| None);
use_state(|| None);
// Dropdown // Dropdown
let project_city_filter: UseStateHandle<Option<String>> = let project_city_filter: UseStateHandle<Option<String>> = use_state(|| None);
use_state(|| None);
// Dropdown // Dropdown
let project_district_filter: UseStateHandle<Option<String>> = let project_district_filter: UseStateHandle<Option<String>> = use_state(|| None);
use_state(|| None); let unit_rooms_filter: UseStateHandle<Option<usize>> = use_state(|| None);
let unit_rooms_filter: UseStateHandle<Option<usize>> =
use_state(|| None);
let search_onclick = { let search_onclick = {
let search_results_handle = search_results_handle.clone(); let search_results_handle = search_results_handle.clone();
@ -167,7 +161,7 @@ pub fn search_page() -> Html {
let cloned_project_type_filter = project_type_filter.clone(); let cloned_project_type_filter = project_type_filter.clone();
Callback::from(move |project_type: Option<ProjectType>| { Callback::from(move |project_type: Option<ProjectType>| {
cloned_project_type_filter.set(project_type) cloned_project_type_filter.set(project_type)
})} })}
options={ vec![ProjectType::Apartamento, ProjectType::Casa, ProjectType::Oficina, ProjectType::Local, ProjectType::Solar] options={ vec![ProjectType::Apartamento, ProjectType::Casa, ProjectType::Oficina, ProjectType::Local, ProjectType::Solar]
} /> } />
</div> </div>
@ -187,7 +181,7 @@ pub fn search_page() -> Html {
let cloned_project_condition_filter = project_condition_filter.clone(); let cloned_project_condition_filter = project_condition_filter.clone();
Callback::from(move |project_condition: Option<ProjectCondition>| { Callback::from(move |project_condition: Option<ProjectCondition>| {
cloned_project_condition_filter.set(project_condition) cloned_project_condition_filter.set(project_condition)
})} })}
options={ vec![ProjectCondition::New, ProjectCondition::Resale]} /> options={ vec![ProjectCondition::New, ProjectCondition::Resale]} />
</div> </div>
@ -226,7 +220,7 @@ pub fn search_page() -> Html {
let project_district_filter = project_district_filter.clone(); let project_district_filter = project_district_filter.clone();
Callback::from(move |project_district: Option<String>| { Callback::from(move |project_district: Option<String>| {
project_district_filter.set(project_district) project_district_filter.set(project_district)
})} })}
options={ (*districts_handle).clone() } /> options={ (*districts_handle).clone() } />
</div> </div>
@ -238,7 +232,7 @@ pub fn search_page() -> Html {
let unit_rooms_filter = unit_rooms_filter.clone(); let unit_rooms_filter = unit_rooms_filter.clone();
Callback::from(move |unit_rooms: Option<usize>| { Callback::from(move |unit_rooms: Option<usize>| {
unit_rooms_filter.set(unit_rooms) unit_rooms_filter.set(unit_rooms)
})} })}
options={ (1..10).collect::<Vec<usize>>() } /> options={ (1..10).collect::<Vec<usize>>() } />
</div> </div>