Finished search logic
This commit is contained in:
parent
3b801c59fe
commit
12095f4141
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -212,6 +212,14 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "err"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "git+https://git.franklinblanco.dev/franklinblanco/err.git#d814091e7367d101197c35e2f7e56a744ce4296b"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "1.9.0"
|
version = "1.9.0"
|
||||||
@ -698,6 +706,7 @@ checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
|
|||||||
name = "jl-frontend"
|
name = "jl-frontend"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"err",
|
||||||
"jl-types",
|
"jl-types",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
@ -726,7 +735,6 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"uuid",
|
"uuid",
|
||||||
"yew",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -27,6 +27,8 @@ uuid = { version = "1.3.0", features = ["v4", "fast-rng", "macro-diagnostics", "
|
|||||||
log = "0.4"
|
log = "0.4"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0.88"
|
serde_json = "1.0.88"
|
||||||
|
err = { git = "https://git.franklinblanco.dev/franklinblanco/err.git" }
|
||||||
|
|
||||||
|
|
||||||
# Core
|
# Core
|
||||||
jl-types = { path = "../jl-types", features = ["yew", "wasm"] }
|
jl-types = { path = "../jl-types", features = ["wasm"] }
|
||||||
|
@ -7,17 +7,26 @@ Divide the Details page into 3 main sections:
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: stretch;
|
justify-content: stretch;
|
||||||
align-items: center;
|
align-items: start;
|
||||||
padding: 0px 30px;
|
padding: 0px 15px;
|
||||||
background-color: black;
|
margin-top: 60px;
|
||||||
margin-top: 30px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.details-head {
|
.details-head {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: stretch;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.details-head-image-frame{
|
.details-head-image-frame {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
height: 300px;
|
||||||
|
width: 100%;
|
||||||
|
background-color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-head-title {
|
||||||
|
font-size: 25px;
|
||||||
}
|
}
|
@ -57,6 +57,18 @@
|
|||||||
background-color:rgba(0, 0, 0, 0.1);
|
background-color:rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.project-search-button {
|
||||||
|
margin-top: 20px;
|
||||||
|
height: 50px;
|
||||||
|
background-color: #252631;
|
||||||
|
color: white;
|
||||||
|
border: 0px;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: large;
|
||||||
|
font-family: Source Sans Pro;
|
||||||
|
font-weight: lighter;
|
||||||
|
}
|
||||||
|
|
||||||
/* Results */
|
/* Results */
|
||||||
|
|
||||||
.project-search-results-container {
|
.project-search-results-container {
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use jl_types::{dto::{filters::Filter, listing::Listing}, domain::project::Project};
|
||||||
|
use reqwest::Method;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use super::base::perform_request_without_client;
|
||||||
|
|
||||||
|
const BASE_URL: &str = "http://localhost:8095/";
|
||||||
|
|
||||||
|
pub async fn get_all_cities() -> Result<HashSet<String>, err::Error> {
|
||||||
|
perform_request_without_client::<String, HashSet<String>>(BASE_URL.into(), Method::GET, "read/locations".into(), None, 200, Vec::new(), None).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_all_districts_in_city(city: &String) -> Result<HashSet<String>, err::Error> {
|
||||||
|
perform_request_without_client::<String, HashSet<String>>(BASE_URL.into(), Method::GET, format!("read/locations/{city}"), None, 200, Vec::new(), None).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_all_projects_with_filters_paged(page: &i64, filters: Vec<Filter>) -> Result<Vec<Project>, err::Error> {
|
||||||
|
perform_request_without_client::<String, Vec<Project>>(BASE_URL.into(), Method::GET, format!("read/projects/{page}"), None, 200, Vec::new(), Some(filters.into_iter().map(|filter| filter.to_param()).collect())).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_project_listing(project_id: &Uuid) -> Result<Listing, err::Error> {
|
||||||
|
perform_request_without_client::<String, Listing>(BASE_URL.into(), Method::GET, format!("read/project/{project_id}"), None, 200, Vec::new(), None).await
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
use err::{Error, MessageResource};
|
||||||
|
use reqwest::Client;
|
||||||
|
use serde::{Serialize, de::DeserializeOwned};
|
||||||
|
|
||||||
|
|
||||||
|
/// This function is mainly for when you don't have a client in your application and just want to get it over with.
|
||||||
|
/// This shouldn't be used as it takes more resource consumption than the above method.
|
||||||
|
pub async fn perform_request_without_client<B: Serialize, R: DeserializeOwned>(
|
||||||
|
base_url: String,
|
||||||
|
method: reqwest::Method,
|
||||||
|
path: String,
|
||||||
|
body: Option<B>,
|
||||||
|
expected_status_code: u16,
|
||||||
|
headers: Vec<(String, String)>,
|
||||||
|
params: Option<Vec<(String, String)>>
|
||||||
|
) -> Result<R, Error> {
|
||||||
|
let client = Client::new();
|
||||||
|
let mut req_incomplete =
|
||||||
|
client.request(method, format!("{url}{path}", url = base_url, path = path));
|
||||||
|
|
||||||
|
for header in headers {
|
||||||
|
req_incomplete = req_incomplete.header(&header.0, &header.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(parameters) = params {
|
||||||
|
req_incomplete = req_incomplete.query(¶meters)
|
||||||
|
}
|
||||||
|
|
||||||
|
let req_complete = match body {
|
||||||
|
Some(b) => req_incomplete.json(&b),
|
||||||
|
None => req_incomplete.header("content-length", 0),
|
||||||
|
};
|
||||||
|
println!("{:?}", req_complete);
|
||||||
|
match req_complete.send().await {
|
||||||
|
// Error handling here
|
||||||
|
Ok(res) => {
|
||||||
|
// Request sent correctly
|
||||||
|
match res.status().as_u16() == expected_status_code {
|
||||||
|
true => {
|
||||||
|
match res.json::<R>().await {
|
||||||
|
Ok(resp_dto) => Ok(resp_dto), // Return correctly deserialized obj
|
||||||
|
Err(err) => Err(Error::Serde(MessageResource::from(err))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false => {
|
||||||
|
//If status code is any other than expected
|
||||||
|
Err(Error::UnexpectedStatusCode(
|
||||||
|
expected_status_code,
|
||||||
|
res.status().as_u16(),
|
||||||
|
match res.json::<Vec<MessageResource>>().await {
|
||||||
|
Ok(messages) => messages,
|
||||||
|
Err(e) => vec![MessageResource::from(e)],
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
// Request couldn't be sent
|
||||||
|
Err(Error::Network(MessageResource::from(e)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -37,14 +37,14 @@ pub fn navigation_bar() -> Html {
|
|||||||
{NAVBAR_COL_LANDING}
|
{NAVBAR_COL_LANDING}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div onclick={move |_| cloned_navigator_2.push(&Route::Search { property_state: ProjectState::Finished })} class={"navbar-item"}>
|
<div onclick={move |_| cloned_navigator_2.push(&Route::Search { project_state: ProjectState::InConstruction })} class={"navbar-item"}>
|
||||||
{NAVBAR_COL_PROYECTOS_ACABADOS}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div onclick={move |_| cloned_navigator_3.push(&Route::Search { property_state: ProjectState::InConstruction })} class={"navbar-item"}>
|
|
||||||
{NAVBAR_COL_PROYECTOS_EN_CONSTRUCCION}
|
{NAVBAR_COL_PROYECTOS_EN_CONSTRUCCION}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div onclick={move |_| cloned_navigator_3.push(&Route::Search { project_state: ProjectState::Finished })} class={"navbar-item"}>
|
||||||
|
{NAVBAR_COL_PROYECTOS_ACABADOS}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div onclick={move |_| cloned_navigator_4.push(&Route::Contact)} class={"navbar-item"}>
|
<div onclick={move |_| cloned_navigator_4.push(&Route::Contact)} class={"navbar-item"}>
|
||||||
{NAVBAR_COL_CONTACTO}
|
{NAVBAR_COL_CONTACTO}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use uuid::Uuid;
|
use jl_types::domain::{project::Project, media::Media};
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use yew_router::prelude::use_navigator;
|
use yew_router::prelude::use_navigator;
|
||||||
|
|
||||||
@ -6,15 +6,24 @@ use crate::routes::main_router::Route;
|
|||||||
|
|
||||||
|
|
||||||
#[function_component(ProjectCard)]
|
#[function_component(ProjectCard)]
|
||||||
pub fn project_card() -> Html {
|
pub fn project_card(props: &ProjectCardProps) -> Html {
|
||||||
let navigator = use_navigator().unwrap();
|
let navigator = use_navigator().unwrap();
|
||||||
|
let project_id = props.project.id.clone();
|
||||||
let project_view_cb = Callback::from(move |_|{
|
let project_view_cb = Callback::from(move |_|{
|
||||||
navigator.push(&Route::Details { property_id: Uuid::default() });
|
navigator.push(&Route::Details { project_id });
|
||||||
});
|
});
|
||||||
|
let cover_image_url;
|
||||||
|
if let Some(first_media) = props.project.media.media_list.get(0) {
|
||||||
|
cover_image_url = match first_media {
|
||||||
|
Media::Photo(url) => url.clone(),
|
||||||
|
Media::Video(_) => String::new(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cover_image_url = String::new()
|
||||||
|
}
|
||||||
html!{
|
html!{
|
||||||
<div class={"project-search-result-card"} onclick={project_view_cb}>
|
<div class={"project-search-result-card"} onclick={project_view_cb}>
|
||||||
<img src={"https://refa.com.do/uploads/posiv.jpg"} alt={"project image"} class={"project-search-result-card-picture"}/>
|
<img src={cover_image_url} alt={"project image"} class={"project-search-result-card-picture"}/>
|
||||||
|
|
||||||
<div class={"project-search-result-card-title"}>
|
<div class={"project-search-result-card-title"}>
|
||||||
{"Suites by refa Piantini"}
|
{"Suites by refa Piantini"}
|
||||||
@ -41,4 +50,9 @@ pub fn project_card() -> Html {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Properties, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct ProjectCardProps {
|
||||||
|
pub project: Project
|
||||||
}
|
}
|
@ -1,21 +1,31 @@
|
|||||||
|
use uuid::Uuid;
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
use crate::components::nav_bar::NavigationBar;
|
use crate::components::nav_bar::NavigationBar;
|
||||||
|
|
||||||
#[function_component(DetailsPage)]
|
#[function_component(DetailsPage)]
|
||||||
pub fn details_page() -> Html {
|
pub fn details_page(_props: &DetailsPageProps) -> Html {
|
||||||
html!{
|
html!{
|
||||||
<>
|
<>
|
||||||
<NavigationBar/>
|
<NavigationBar/>
|
||||||
<div class={"page-container"}>
|
<div class={"page-container"}>
|
||||||
<div class={"details-container"}>
|
<div class={"details-container"}>
|
||||||
<div class={"details-head"}>
|
<div class={"details-head"}>
|
||||||
<div class={""}>
|
<div class={"details-head-image-frame"}>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class={"details-head-title"}>{"Suites by refa piantini"}</div>
|
||||||
|
<div class={"details-head-price"}>{"RD$123,130.00"}</div>
|
||||||
|
<div>{"Andres Julio Aybar #39"}</div>
|
||||||
|
<div>{"Descripción"}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Properties, PartialEq)]
|
||||||
|
pub struct DetailsPageProps {
|
||||||
|
pub project_id: Uuid
|
||||||
}
|
}
|
@ -1,29 +1,63 @@
|
|||||||
use jl_types::domain::{project_state::ProjectState, project_type::ProjectType, project_condition::ProjectCondition};
|
use jl_types::{domain::{project_state::ProjectState, project_type::ProjectType, project_condition::ProjectCondition, project::Project}, dto::filters::Filter};
|
||||||
use log::info;
|
use log::info;
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use yew_utils::{components::drop_down::{DropDownProps, DropDown}, vdom::comp_with};
|
use yew_utils::{components::drop_down::{DropDownProps, DropDown}, vdom::comp_with};
|
||||||
|
|
||||||
use jl_types::domain::option_wrapper::OptionWrapper;
|
use jl_types::domain::option_wrapper::OptionWrapper;
|
||||||
use crate::components::{nav_bar::NavigationBar, project_card::ProjectCard};
|
use crate::{components::{nav_bar::NavigationBar, project_card::ProjectCard}, api::backend::{get_all_cities, get_all_districts_in_city, get_all_projects_with_filters_paged}};
|
||||||
|
|
||||||
|
|
||||||
#[function_component(SearchPage)]
|
#[function_component(SearchPage)]
|
||||||
pub fn search_page(_props: &SearchPageProperties) -> Html {
|
pub fn search_page(props: &SearchPageProperties) -> Html {
|
||||||
|
// let force_update_trigger = use_force_update();
|
||||||
|
let cities_handle = use_state(|| Vec::from([OptionWrapper::new(None)]));
|
||||||
|
let districts_handle = use_state(|| Vec::from([OptionWrapper::new(None)]));
|
||||||
|
let search_results_handle: UseStateHandle<Vec<Project>> = use_state(|| Vec::new());
|
||||||
|
let page_counter: UseStateHandle<i64> = use_state(|| 1);
|
||||||
|
|
||||||
|
let mut filters = Vec::new();
|
||||||
|
if props.project_state.eq(&ProjectState::Finished) {
|
||||||
|
filters.push(Filter::Finished);
|
||||||
|
}
|
||||||
|
|
||||||
|
// All code to execute on first render and never again
|
||||||
|
use_state(|| {
|
||||||
|
let cities_handle = cities_handle.clone();
|
||||||
|
let search_results_handle = search_results_handle.clone();
|
||||||
|
let page_counter = page_counter.clone();
|
||||||
|
wasm_bindgen_futures::spawn_local(async move {
|
||||||
|
match get_all_cities().await {
|
||||||
|
Ok(cities) => {
|
||||||
|
let mut cities: Vec<OptionWrapper<String>> = cities.into_iter().map(|location| OptionWrapper::new(Some(location))).collect();
|
||||||
|
cities.insert(0, OptionWrapper::new(None));
|
||||||
|
cities_handle.set(cities);
|
||||||
|
},
|
||||||
|
Err(error) => info!("Error in loading cities: {error}")
|
||||||
|
};
|
||||||
|
match get_all_projects_with_filters_paged(&(*page_counter), filters).await {
|
||||||
|
Ok(projects) => {
|
||||||
|
search_results_handle.set(projects)
|
||||||
|
},
|
||||||
|
Err(error) => info!("Error in loading projects: {error}"),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Dropdown
|
// Dropdown
|
||||||
let project_type_filter: UseStateHandle<OptionWrapper<ProjectType>> = use_state(|| OptionWrapper::new(None));
|
let project_type_filter: UseStateHandle<OptionWrapper<ProjectType>> = use_state(|| OptionWrapper::new(None));
|
||||||
// Dropdown
|
// Dropdown
|
||||||
let project_condition_filter: UseStateHandle<OptionWrapper<ProjectCondition>> = use_state(|| OptionWrapper::new(None));
|
let project_condition_filter: UseStateHandle<OptionWrapper<ProjectCondition>> = use_state(|| OptionWrapper::new(None));
|
||||||
// Dropdown
|
// Dropdown
|
||||||
let project_state_filter: UseStateHandle<OptionWrapper<ProjectState>> = use_state(|| OptionWrapper::new(None));
|
// let project_state_filter: UseStateHandle<OptionWrapper<ProjectState>> = use_state(|| OptionWrapper::new(Some(props.project_state.clone())));
|
||||||
// Dropdown
|
// Dropdown
|
||||||
let project_city_filter: UseStateHandle<OptionWrapper<String>> = use_state(|| OptionWrapper::new(None));
|
let project_city_filter: UseStateHandle<OptionWrapper<String>> = use_state(|| OptionWrapper::new(None));
|
||||||
// Dropdown
|
// Dropdown
|
||||||
let project_district_filter: UseStateHandle<OptionWrapper<String>> = use_state(|| OptionWrapper::new(None));
|
let project_district_filter: UseStateHandle<OptionWrapper<String>> = use_state(|| OptionWrapper::new(None));
|
||||||
//TODO: Think about price filtering
|
//TODO: Think about price filtering
|
||||||
// TextField
|
/*// TextField
|
||||||
let _project_min_price_filter: UseStateHandle<OptionWrapper<f64>> = use_state(|| OptionWrapper::new(None));
|
let _project_min_price_filter: UseStateHandle<OptionWrapper<f64>> = use_state(|| OptionWrapper::new(None));
|
||||||
// TextField
|
// TextField
|
||||||
let _project_max_price_filter: UseStateHandle<OptionWrapper<f64>> = use_state(|| OptionWrapper::new(None));
|
let _project_max_price_filter: UseStateHandle<OptionWrapper<f64>> = use_state(|| OptionWrapper::new(None));*/
|
||||||
|
|
||||||
|
|
||||||
let project_type_drop_down = comp_with::<DropDown<OptionWrapper<ProjectType>>>(DropDownProps {
|
let project_type_drop_down = comp_with::<DropDown<OptionWrapper<ProjectType>>>(DropDownProps {
|
||||||
@ -39,9 +73,11 @@ pub fn search_page(_props: &SearchPageProperties) -> Html {
|
|||||||
class_css: Some("project-search-filter-item".into())
|
class_css: Some("project-search-filter-item".into())
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
// TODO: Fix ProjectState tostring printing the db insertable
|
||||||
let project_state_drop_down = comp_with::<DropDown<OptionWrapper<ProjectState>>>(DropDownProps {
|
let project_state_drop_down = comp_with::<DropDown<OptionWrapper<ProjectState>>>(DropDownProps {
|
||||||
initial: OptionWrapper::new(None),
|
initial: (*project_state_filter).clone(),
|
||||||
options: vec![OptionWrapper::new(None), OptionWrapper::new(Some(ProjectState::InConstruction)), OptionWrapper::new(Some(ProjectState::Finished)) ],
|
options: vec![OptionWrapper::new(Some(ProjectState::InConstruction)), OptionWrapper::new(Some(ProjectState::Finished)) ],
|
||||||
selection_changed: {
|
selection_changed: {
|
||||||
let cloned_project_state_filter = project_state_filter.clone();
|
let cloned_project_state_filter = project_state_filter.clone();
|
||||||
Callback::from(move |project_state: OptionWrapper<ProjectState>| {
|
Callback::from(move |project_state: OptionWrapper<ProjectState>| {
|
||||||
@ -50,7 +86,7 @@ pub fn search_page(_props: &SearchPageProperties) -> Html {
|
|||||||
}
|
}
|
||||||
)},
|
)},
|
||||||
class_css: Some("project-search-filter-item".into())
|
class_css: Some("project-search-filter-item".into())
|
||||||
});
|
});*/
|
||||||
|
|
||||||
let project_condition_drop_down = comp_with::<DropDown<OptionWrapper<ProjectCondition>>>(DropDownProps {
|
let project_condition_drop_down = comp_with::<DropDown<OptionWrapper<ProjectCondition>>>(DropDownProps {
|
||||||
initial: OptionWrapper::new(None),
|
initial: OptionWrapper::new(None),
|
||||||
@ -67,12 +103,25 @@ pub fn search_page(_props: &SearchPageProperties) -> Html {
|
|||||||
|
|
||||||
let project_city_drop_down = comp_with::<DropDown<OptionWrapper<String>>>(DropDownProps {
|
let project_city_drop_down = comp_with::<DropDown<OptionWrapper<String>>>(DropDownProps {
|
||||||
initial: OptionWrapper::new(None),
|
initial: OptionWrapper::new(None),
|
||||||
options: vec![OptionWrapper::new(None), OptionWrapper::new(Some("Santo Domingo".into())), OptionWrapper::new(Some("Punta Cana".into())) ],
|
options: (*cities_handle).clone(),
|
||||||
selection_changed: {
|
selection_changed: {
|
||||||
let cloned_project_city_filter = project_city_filter.clone();
|
let cloned_project_city_filter = project_city_filter.clone();
|
||||||
|
let districts_handle = districts_handle.clone();
|
||||||
|
|
||||||
Callback::from(move |project_city: OptionWrapper<String>| {
|
Callback::from(move |project_city: OptionWrapper<String>| {
|
||||||
|
let districts_handle = districts_handle.clone();
|
||||||
info!("{}", project_city.to_string());
|
info!("{}", project_city.to_string());
|
||||||
cloned_project_city_filter.set(project_city)
|
cloned_project_city_filter.set(project_city.clone());
|
||||||
|
wasm_bindgen_futures::spawn_local(async move {
|
||||||
|
match get_all_districts_in_city(&project_city.to_string()).await {
|
||||||
|
Ok(districts) => {
|
||||||
|
let mut districts_vec: Vec<OptionWrapper<String>> = districts.into_iter().map(|district| OptionWrapper::new(Some(district))).collect();
|
||||||
|
districts_vec.insert(0, OptionWrapper::new(None));
|
||||||
|
districts_handle.set(districts_vec);
|
||||||
|
},
|
||||||
|
Err(error) => info!("Error in dropdown callback: {}", error),
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
)},
|
)},
|
||||||
class_css: Some("project-search-filter-item".into())
|
class_css: Some("project-search-filter-item".into())
|
||||||
@ -81,7 +130,7 @@ pub fn search_page(_props: &SearchPageProperties) -> Html {
|
|||||||
//TODO: District dropdown should only show districts in city, otherwise show nothing or disabled
|
//TODO: District dropdown should only show districts in city, otherwise show nothing or disabled
|
||||||
let project_district_drop_down = comp_with::<DropDown<OptionWrapper<String>>>(DropDownProps {
|
let project_district_drop_down = comp_with::<DropDown<OptionWrapper<String>>>(DropDownProps {
|
||||||
initial: OptionWrapper::new(None),
|
initial: OptionWrapper::new(None),
|
||||||
options: vec![OptionWrapper::new(None), OptionWrapper::new(Some("Evaristo Morales".into())), OptionWrapper::new(Some("Cap Cana".into())) ],
|
options: (*districts_handle).clone(),
|
||||||
selection_changed: {
|
selection_changed: {
|
||||||
let cloned_project_district_filter = project_district_filter.clone();
|
let cloned_project_district_filter = project_district_filter.clone();
|
||||||
Callback::from(move |project_district: OptionWrapper<String>| {
|
Callback::from(move |project_district: OptionWrapper<String>| {
|
||||||
@ -92,6 +141,50 @@ pub fn search_page(_props: &SearchPageProperties) -> Html {
|
|||||||
class_css: Some("project-search-filter-item".into())
|
class_css: Some("project-search-filter-item".into())
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let search_onclick = {
|
||||||
|
let search_results_handle = search_results_handle.clone();
|
||||||
|
let page_counter = page_counter.clone();
|
||||||
|
let project_type_filter = project_type_filter.clone();
|
||||||
|
let project_condition_filter = project_condition_filter.clone();
|
||||||
|
let project_city_filter = project_city_filter.clone();
|
||||||
|
let project_district_filter = project_district_filter.clone();
|
||||||
|
|
||||||
|
let props = props.clone();
|
||||||
|
|
||||||
|
Callback::from(move |_| {
|
||||||
|
let mut filters = Vec::new();
|
||||||
|
if props.project_state.eq(&ProjectState::Finished) {
|
||||||
|
filters.push(Filter::Finished);
|
||||||
|
}
|
||||||
|
match &(*project_type_filter).option {
|
||||||
|
Some(project_type) => filters.push(Filter::ByProjectType(project_type.clone())),
|
||||||
|
None => {},
|
||||||
|
};
|
||||||
|
match &(*project_condition_filter).option {
|
||||||
|
Some(project_condition) => filters.push(Filter::ByProjectCondition(project_condition.clone())),
|
||||||
|
None => {},
|
||||||
|
};
|
||||||
|
match &(*project_city_filter).option {
|
||||||
|
Some(project_city) => filters.push(Filter::InCity(project_city.clone())),
|
||||||
|
None => {},
|
||||||
|
};
|
||||||
|
match &(*project_district_filter).option {
|
||||||
|
Some(project_district) => filters.push(Filter::InDistrict(project_district.clone())),
|
||||||
|
None => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
let search_results_handle = search_results_handle.clone();
|
||||||
|
let page_counter = page_counter.clone();
|
||||||
|
wasm_bindgen_futures::spawn_local(async move {
|
||||||
|
match get_all_projects_with_filters_paged(&(*page_counter), filters).await {
|
||||||
|
Ok(projects) => {
|
||||||
|
search_results_handle.set(projects)
|
||||||
|
},
|
||||||
|
Err(error) => info!("Error in loading projects: {error}"),
|
||||||
|
};
|
||||||
|
info!("done");
|
||||||
|
});
|
||||||
|
})};
|
||||||
html!{
|
html!{
|
||||||
<>
|
<>
|
||||||
<NavigationBar/>
|
<NavigationBar/>
|
||||||
@ -105,12 +198,12 @@ pub fn search_page(_props: &SearchPageProperties) -> Html {
|
|||||||
{project_type_drop_down}
|
{project_type_drop_down}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class={"project-search-filter-container"}>
|
/*<div class={"project-search-filter-container"}>
|
||||||
<div class={"project-search-filter-label"}>
|
<div class={"project-search-filter-label"}>
|
||||||
{"Estatus de Proyecto"}
|
{"Estatus de Proyecto"}
|
||||||
</div>
|
</div>
|
||||||
{project_state_drop_down}
|
{project_state_drop_down}
|
||||||
</div>
|
</div>*/
|
||||||
|
|
||||||
<div class={"project-search-filter-container"}>
|
<div class={"project-search-filter-container"}>
|
||||||
<div class={"project-search-filter-label"}>
|
<div class={"project-search-filter-label"}>
|
||||||
@ -132,15 +225,16 @@ pub fn search_page(_props: &SearchPageProperties) -> Html {
|
|||||||
</div>
|
</div>
|
||||||
{project_district_drop_down}
|
{project_district_drop_down}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button class={"project-search-button"} onclick={search_onclick}>
|
||||||
|
{"Buscar"}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class={"project-search-divider"}/>
|
<div class={"project-search-divider"}/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class={"project-search-results-container"}> // Search Results Content
|
<div class={"project-search-results-container"}> // Search Results Content
|
||||||
<ProjectCard/>
|
{(*search_results_handle).clone().into_iter().map(|project| html!{<ProjectCard {project}/>}).collect::<Html>()}
|
||||||
<ProjectCard/>
|
|
||||||
<ProjectCard/>
|
|
||||||
<ProjectCard/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@ -149,5 +243,5 @@ pub fn search_page(_props: &SearchPageProperties) -> Html {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Properties)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Properties)]
|
||||||
pub struct SearchPageProperties {
|
pub struct SearchPageProperties {
|
||||||
pub property_state: ProjectState
|
pub project_state: ProjectState,
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,10 @@ use crate::{pages::{landing::LandingPage, search::{SearchPage}, details::Details
|
|||||||
pub enum Route {
|
pub enum Route {
|
||||||
#[at("/")]
|
#[at("/")]
|
||||||
LandingPage,
|
LandingPage,
|
||||||
#[at("/search/:property_state")]
|
#[at("/search/:project_state")]
|
||||||
Search { property_state: ProjectState },
|
Search { project_state: ProjectState },
|
||||||
#[at("/details/:property_id")]
|
#[at("/details/:project_id")]
|
||||||
Details { property_id: Uuid },
|
Details { project_id: Uuid },
|
||||||
#[at("/contact")]
|
#[at("/contact")]
|
||||||
Contact,
|
Contact,
|
||||||
|
|
||||||
@ -25,8 +25,8 @@ pub enum Route {
|
|||||||
pub fn switch(routes: Route) -> Html {
|
pub fn switch(routes: Route) -> Html {
|
||||||
match routes {
|
match routes {
|
||||||
Route::LandingPage => html! { <LandingPage/> },
|
Route::LandingPage => html! { <LandingPage/> },
|
||||||
Route::Search { property_state } => html! { <SearchPage property_state={property_state}/> },
|
Route::Search { project_state } => html! { <SearchPage project_state={project_state}/> },
|
||||||
Route::Details { property_id: _ } => html! { <DetailsPage/> },
|
Route::Details { project_id } => html! { <DetailsPage project_id={project_id}/> },
|
||||||
Route::NotFound => html! { <NotFoundPage/> },
|
Route::NotFound => html! { <NotFoundPage/> },
|
||||||
Route::Contact => html! { <ContactPage/> }
|
Route::Contact => html! { <ContactPage/> }
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user