jl-frontend/src/pages/search.rs

248 lines
12 KiB
Rust

use jl_types::{domain::{project_state::ProjectState, project_type::ProjectType, project_condition::ProjectCondition, project::Project}, dto::{filters::Filter, project_card::ProjectCardDto}};
use log::info;
use yew::prelude::*;
use yew_utils::{components::drop_down::{DropDownProps, DropDown}, vdom::comp_with};
use jl_types::domain::option_wrapper::OptionWrapper;
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)]
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<ProjectCardDto>> = 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
let project_type_filter: UseStateHandle<OptionWrapper<ProjectType>> = use_state(|| OptionWrapper::new(None));
// Dropdown
let project_condition_filter: UseStateHandle<OptionWrapper<ProjectCondition>> = use_state(|| OptionWrapper::new(None));
// Dropdown
// let project_state_filter: UseStateHandle<OptionWrapper<ProjectState>> = use_state(|| OptionWrapper::new(Some(props.project_state.clone())));
// Dropdown
let project_city_filter: UseStateHandle<OptionWrapper<String>> = use_state(|| OptionWrapper::new(None));
// Dropdown
let project_district_filter: UseStateHandle<OptionWrapper<String>> = use_state(|| OptionWrapper::new(None));
//TODO: Think about price filtering
/*// TextField
let _project_min_price_filter: UseStateHandle<OptionWrapper<f64>> = use_state(|| OptionWrapper::new(None));
// TextField
let _project_max_price_filter: UseStateHandle<OptionWrapper<f64>> = use_state(|| OptionWrapper::new(None));*/
let project_type_drop_down = comp_with::<DropDown<OptionWrapper<ProjectType>>>(DropDownProps {
initial: OptionWrapper::new(None),
options: vec![OptionWrapper::new(None), OptionWrapper::new(Some(ProjectType::Apartamento)), OptionWrapper::new(Some(ProjectType::Casa)), OptionWrapper::new(Some(ProjectType::Oficina)), OptionWrapper::new(Some(ProjectType::Local)), OptionWrapper::new(Some(ProjectType::Solar)) ],
selection_changed: {
let cloned_project_type_filter = project_type_filter.clone();
Callback::from(move |project_type: OptionWrapper<ProjectType>| {
info!("{}", project_type.to_string());
cloned_project_type_filter.set(project_type)
}
)},
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 {
initial: (*project_state_filter).clone(),
options: vec![OptionWrapper::new(Some(ProjectState::InConstruction)), OptionWrapper::new(Some(ProjectState::Finished)) ],
selection_changed: {
let cloned_project_state_filter = project_state_filter.clone();
Callback::from(move |project_state: OptionWrapper<ProjectState>| {
info!("{}", project_state.to_string());
cloned_project_state_filter.set(project_state)
}
)},
class_css: Some("project-search-filter-item".into())
});*/
let project_condition_drop_down = comp_with::<DropDown<OptionWrapper<ProjectCondition>>>(DropDownProps {
initial: OptionWrapper::new(None),
options: vec![OptionWrapper::new(None), OptionWrapper::new(Some(ProjectCondition::New)), OptionWrapper::new(Some(ProjectCondition::Resale)) ],
selection_changed: {
let cloned_project_condition_filter = project_condition_filter.clone();
Callback::from(move |project_condition: OptionWrapper<ProjectCondition>| {
info!("{}", project_condition.to_string());
cloned_project_condition_filter.set(project_condition)
}
)},
class_css: Some("project-search-filter-item".into())
});
let project_city_drop_down = comp_with::<DropDown<OptionWrapper<String>>>(DropDownProps {
initial: OptionWrapper::new(None),
options: (*cities_handle).clone(),
selection_changed: {
let cloned_project_city_filter = project_city_filter.clone();
let districts_handle = districts_handle.clone();
Callback::from(move |project_city: OptionWrapper<String>| {
let districts_handle = districts_handle.clone();
info!("{}", project_city.to_string());
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())
});
//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 {
initial: OptionWrapper::new(None),
options: (*districts_handle).clone(),
selection_changed: {
let cloned_project_district_filter = project_district_filter.clone();
Callback::from(move |project_district: OptionWrapper<String>| {
info!("{}", project_district.to_string());
cloned_project_district_filter.set(project_district)
}
)},
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!{
<>
<NavigationBar/>
<div class={"page-container"}>
<div class={"project-search-container"}>
<div class={"project-search-filters-container"}> // Filters
<div class={"project-search-filter-container"}>
<div class={"project-search-filter-label"}>
{"Tipo de Proyecto"}
</div>
{project_type_drop_down}
</div>
/*<div class={"project-search-filter-container"}>
<div class={"project-search-filter-label"}>
{"Estatus de Proyecto"}
</div>
{project_state_drop_down}
</div>*/
<div class={"project-search-filter-container"}>
<div class={"project-search-filter-label"}>
{"Condición de Proyecto"}
</div>
{project_condition_drop_down}
</div>
<div class={"project-search-filter-container"}>
<div class={"project-search-filter-label"}>
{"Ciudad"}
</div>
{project_city_drop_down}
</div>
<div class={"project-search-filter-container"}>
<div class={"project-search-filter-label"}>
{"Sector"}
</div>
{project_district_drop_down}
</div>
<button class={"project-search-button"} onclick={search_onclick}>
{"Buscar"}
</button>
</div>
<div class={"project-search-divider"}/>
</div>
<div class={"project-search-results-container"}> // Search Results Content
{(*search_results_handle).clone().into_iter().map(|project| html!{<ProjectCard {project}/>}).collect::<Html>()}
</div>
</div>
</>
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Properties)]
pub struct SearchPageProperties {
pub project_state: ProjectState,
}