Cargo fmt holy
This commit is contained in:
parent
6b20ac66f1
commit
fc2a2510ec
@ -32,6 +32,5 @@ err = { git = "https://git.franklinblanco.dev/franklinblanco/err.git" }
|
|||||||
thousands = "0.2.0"
|
thousands = "0.2.0"
|
||||||
chrono = "0.4.23"
|
chrono = "0.4.23"
|
||||||
|
|
||||||
|
|
||||||
# Core
|
# Core
|
||||||
jl-types = { path = "../jl-types", features = ["wasm"] }
|
jl-types = { path = "../jl-types", features = ["wasm"] }
|
||||||
|
77
css/components/mediapicker.css
Normal file
77
css/components/mediapicker.css
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
.mediapicker-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
justify-content: start;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.mediapicker-media {
|
||||||
|
display: flex;
|
||||||
|
object-fit: cover;
|
||||||
|
width: 240px;
|
||||||
|
height: 240px;
|
||||||
|
}
|
||||||
|
.mediapicker-new {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
align-items: center;
|
||||||
|
width: 240px;
|
||||||
|
height: 240px;
|
||||||
|
|
||||||
|
font-size: 40px;
|
||||||
|
background-color: gainsboro;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.mediapicker-new:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.mediapicker-delete {
|
||||||
|
position: relative;
|
||||||
|
right: 25px;
|
||||||
|
top: 10px;
|
||||||
|
|
||||||
|
color: grey;
|
||||||
|
font-size: 18px;
|
||||||
|
width: fit-content;
|
||||||
|
height: fit-content;
|
||||||
|
}
|
||||||
|
.mediapicker-delete:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.mediapicker-new-photo-section {
|
||||||
|
width: 90%;
|
||||||
|
height: 30px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-family: Source Sans Pro;
|
||||||
|
background-color: white;
|
||||||
|
color: black;
|
||||||
|
|
||||||
|
padding: 3px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.mediapicker-new-video-section {
|
||||||
|
width: 90%;
|
||||||
|
height: 30px;
|
||||||
|
background-color: white;
|
||||||
|
color: black;
|
||||||
|
|
||||||
|
padding: 3px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.mediapicker-new-video-section-input {
|
||||||
|
height: 100%;
|
||||||
|
background-color: white;
|
||||||
|
border: solid 0.5px #d8d8d8;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
text-indent: 10px;
|
||||||
|
font-family: Source Sans Pro;
|
||||||
|
}
|
@ -30,6 +30,7 @@
|
|||||||
<link data-trunk type="text/css" href="css/components/admin_nav_bar.css" rel="css" />
|
<link data-trunk type="text/css" href="css/components/admin_nav_bar.css" rel="css" />
|
||||||
<link data-trunk type="text/css" href="css/components/admin_project.css" rel="css" />
|
<link data-trunk type="text/css" href="css/components/admin_project.css" rel="css" />
|
||||||
<link data-trunk type="text/css" href="css/components/datepicker.css" rel="css" />
|
<link data-trunk type="text/css" href="css/components/datepicker.css" rel="css" />
|
||||||
|
<link data-trunk type="text/css" href="css/components/mediapicker.css" rel="css" />
|
||||||
|
|
||||||
|
|
||||||
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro" rel="stylesheet">
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use jl_types::{dto::{filters::Filter, listing::Listing, project_card::ProjectCardDto, payloads::contact::ContactPayload}, domain::{agent::Agent, count::Count, contact::Contact}};
|
use jl_types::{
|
||||||
|
domain::{agent::Agent, contact::Contact, count::Count},
|
||||||
|
dto::{
|
||||||
|
filters::Filter, listing::Listing, payloads::contact::ContactPayload,
|
||||||
|
project_card::ProjectCardDto,
|
||||||
|
},
|
||||||
|
};
|
||||||
use reqwest::Method;
|
use reqwest::Method;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
@ -9,38 +15,140 @@ use super::base::perform_request_without_client;
|
|||||||
const BASE_URL: &str = "http://localhost:8095/";
|
const BASE_URL: &str = "http://localhost:8095/";
|
||||||
|
|
||||||
pub async fn get_all_cities() -> Result<HashSet<String>, err::Error> {
|
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
|
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> {
|
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
|
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<ProjectCardDto>, err::Error> {
|
pub async fn get_all_projects_with_filters_paged(
|
||||||
perform_request_without_client::<String, Vec<ProjectCardDto>>(BASE_URL.into(), Method::GET, format!("read/projects/{page}"), None, 200, Vec::new(), Some(filters.into_iter().map(|filter| filter.to_param()).collect())).await
|
page: &i64,
|
||||||
|
filters: Vec<Filter>,
|
||||||
|
) -> Result<Vec<ProjectCardDto>, err::Error> {
|
||||||
|
perform_request_without_client::<String, Vec<ProjectCardDto>>(
|
||||||
|
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> {
|
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
|
perform_request_without_client::<String, Listing>(
|
||||||
|
BASE_URL.into(),
|
||||||
|
Method::GET,
|
||||||
|
format!("read/project/{project_id}"),
|
||||||
|
None,
|
||||||
|
200,
|
||||||
|
Vec::new(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_all_agents() -> Result<Vec<Agent>, err::Error> {
|
pub async fn get_all_agents() -> Result<Vec<Agent>, err::Error> {
|
||||||
perform_request_without_client::<String, Vec<Agent>>(BASE_URL.into(), Method::GET, format!("read/agent"), None, 200, Vec::new(), None).await
|
perform_request_without_client::<String, Vec<Agent>>(
|
||||||
|
BASE_URL.into(),
|
||||||
|
Method::GET,
|
||||||
|
format!("read/agent"),
|
||||||
|
None,
|
||||||
|
200,
|
||||||
|
Vec::new(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_agent_with_shortcode(shortcode: &String) -> Result<Agent, err::Error> {
|
pub async fn get_agent_with_shortcode(shortcode: &String) -> Result<Agent, err::Error> {
|
||||||
perform_request_without_client::<String, Agent>(BASE_URL.into(), Method::GET, format!("read/agent/{shortcode}"), None, 200, Vec::new(), None).await
|
perform_request_without_client::<String, Agent>(
|
||||||
|
BASE_URL.into(),
|
||||||
|
Method::GET,
|
||||||
|
format!("read/agent/{shortcode}"),
|
||||||
|
None,
|
||||||
|
200,
|
||||||
|
Vec::new(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_all_page_visits_count() -> Result<Count, err::Error> {
|
pub async fn get_all_page_visits_count() -> Result<Count, err::Error> {
|
||||||
perform_request_without_client::<String, Count>(BASE_URL.into(), Method::GET, format!("admin/visits/count"), None, 200, Vec::new(), None).await
|
perform_request_without_client::<String, Count>(
|
||||||
|
BASE_URL.into(),
|
||||||
|
Method::GET,
|
||||||
|
format!("admin/visits/count"),
|
||||||
|
None,
|
||||||
|
200,
|
||||||
|
Vec::new(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
pub async fn get_all_contacts_count() -> Result<Count, err::Error> {
|
pub async fn get_all_contacts_count() -> Result<Count, err::Error> {
|
||||||
perform_request_without_client::<String, Count>(BASE_URL.into(), Method::GET, format!("admin/contacts/count"), None, 200, Vec::new(), None).await
|
perform_request_without_client::<String, Count>(
|
||||||
|
BASE_URL.into(),
|
||||||
|
Method::GET,
|
||||||
|
format!("admin/contacts/count"),
|
||||||
|
None,
|
||||||
|
200,
|
||||||
|
Vec::new(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
pub async fn get_all_contacts() -> Result<Vec<Contact>, err::Error> {
|
pub async fn get_all_contacts() -> Result<Vec<Contact>, err::Error> {
|
||||||
perform_request_without_client::<String, Vec<Contact>>(BASE_URL.into(), Method::GET, format!("admin/contacts"), None, 200, Vec::new(), None).await
|
perform_request_without_client::<String, Vec<Contact>>(
|
||||||
|
BASE_URL.into(),
|
||||||
|
Method::GET,
|
||||||
|
format!("admin/contacts"),
|
||||||
|
None,
|
||||||
|
200,
|
||||||
|
Vec::new(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
pub async fn create_new_contact_request(contact: ContactPayload) -> Result<(), err::Error> {
|
pub async fn create_new_contact_request(contact: ContactPayload) -> Result<(), err::Error> {
|
||||||
perform_request_without_client(BASE_URL.into(), Method::POST, format!("read/contact"), Some(contact), 200, Vec::new(), None).await
|
perform_request_without_client(
|
||||||
|
BASE_URL.into(),
|
||||||
|
Method::POST,
|
||||||
|
format!("read/contact"),
|
||||||
|
Some(contact),
|
||||||
|
200,
|
||||||
|
Vec::new(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
pub async fn upload_image() -> Result<String, err::Error> {
|
||||||
|
perform_request_without_client(BASE_URL.into(), Method::POST, format!("images/"), Some(), 200, Vec::new(), None).await
|
||||||
|
}*/
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use err::{Error, MessageResource};
|
use err::{Error, MessageResource};
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use serde::{Serialize, de::DeserializeOwned};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
|
|
||||||
|
|
||||||
/// This function is mainly for when you don't have a client in your application and just want to get it over with.
|
/// 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.
|
/// This shouldn't be used as it takes more resource consumption than the above method.
|
||||||
@ -12,7 +11,7 @@ pub async fn perform_request_without_client<B: Serialize, R: DeserializeOwned>(
|
|||||||
body: Option<B>,
|
body: Option<B>,
|
||||||
expected_status_code: u16,
|
expected_status_code: u16,
|
||||||
headers: Vec<(String, String)>,
|
headers: Vec<(String, String)>,
|
||||||
params: Option<Vec<(String, String)>>
|
params: Option<Vec<(String, String)>>,
|
||||||
) -> Result<R, Error> {
|
) -> Result<R, Error> {
|
||||||
let client = Client::new();
|
let client = Client::new();
|
||||||
let mut req_incomplete =
|
let mut req_incomplete =
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
pub mod base;
|
|
||||||
pub mod backend;
|
pub mod backend;
|
||||||
|
pub mod base;
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use yew_router::prelude::{use_navigator, use_route};
|
use yew_router::prelude::{use_navigator, use_route};
|
||||||
|
|
||||||
use crate::{routes::main_router::Route};
|
use crate::routes::main_router::Route;
|
||||||
//use yew_router::prelude::use_navigator;
|
//use yew_router::prelude::use_navigator;
|
||||||
|
|
||||||
|
|
||||||
#[function_component(AdminNavigationBar)]
|
#[function_component(AdminNavigationBar)]
|
||||||
pub fn admin_navigation_bar() -> Html {
|
pub fn admin_navigation_bar() -> Html {
|
||||||
let current_route: Option<Route> = use_route();
|
let current_route: Option<Route> = use_route();
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
use jl_types::{dto::project_card::ProjectCardDto, domain::project_state::ProjectState};
|
use jl_types::{domain::project_state::ProjectState, dto::project_card::ProjectCardDto};
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use yew_router::{prelude::use_navigator};
|
use yew_router::prelude::use_navigator;
|
||||||
|
|
||||||
use crate::{routes::main_router::Route, pages::admin::edit::{EditType, EditItem}};
|
use crate::{
|
||||||
|
pages::admin::edit::{EditItem, EditType},
|
||||||
|
routes::main_router::Route,
|
||||||
|
};
|
||||||
|
|
||||||
#[function_component(AdminProject)]
|
#[function_component(AdminProject)]
|
||||||
pub fn admin_project(props: &AdminProjectProps) -> Html {
|
pub fn admin_project(props: &AdminProjectProps) -> Html {
|
||||||
@ -24,7 +27,10 @@ pub fn admin_project(props: &AdminProjectProps) -> Html {
|
|||||||
let onclick_item = {
|
let onclick_item = {
|
||||||
let props = props.clone();
|
let props = props.clone();
|
||||||
Callback::from(move |_| {
|
Callback::from(move |_| {
|
||||||
navigator.push(&Route::AdminEdit { edit_type: EditType::Existing(props.project.id), edit_item: EditItem::Project });
|
navigator.push(&Route::AdminEdit {
|
||||||
|
edit_type: EditType::Existing(props.project.id),
|
||||||
|
edit_item: EditItem::Project,
|
||||||
|
});
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
html! {
|
html! {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use chrono::{NaiveDate};
|
use chrono::NaiveDate;
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
use crate::components::textfield::get_value_from_input_event;
|
use crate::components::textfield::get_value_from_input_event;
|
||||||
@ -15,11 +15,11 @@ pub fn datepicker(props: &DatePickerProps) -> Html {
|
|||||||
Ok(date) => {
|
Ok(date) => {
|
||||||
match optional_cb.clone() {
|
match optional_cb.clone() {
|
||||||
Some(callback) => callback.emit(Some(date.clone())),
|
Some(callback) => callback.emit(Some(date.clone())),
|
||||||
None => {},
|
None => {}
|
||||||
};
|
};
|
||||||
date_handle.set(Some(date));
|
date_handle.set(Some(date));
|
||||||
},
|
}
|
||||||
Err(_) => {},
|
Err(_) => {}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
let date_handle = props.value.clone();
|
let date_handle = props.value.clone();
|
||||||
@ -40,7 +40,7 @@ pub struct DatePickerProps {
|
|||||||
pub value: UseStateHandle<Option<NaiveDate>>,
|
pub value: UseStateHandle<Option<NaiveDate>>,
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
pub required: bool,
|
pub required: bool,
|
||||||
pub onchange: Option<Callback<Option<NaiveDate>>>
|
pub onchange: Option<Callback<Option<NaiveDate>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_date(date_str: String) -> Result<NaiveDate, ()> {
|
pub fn parse_date(date_str: String) -> Result<NaiveDate, ()> {
|
||||||
@ -49,6 +49,6 @@ pub fn parse_date(date_str: String) -> Result<NaiveDate, ()> {
|
|||||||
Err(error) => {
|
Err(error) => {
|
||||||
log::error!("Falied to parse Date in DatePicker: {error}");
|
log::error!("Falied to parse Date in DatePicker: {error}");
|
||||||
Err(())
|
Err(())
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,12 +1,14 @@
|
|||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
use jl_types::domain::{option_wrapper::OptionWrapper};
|
use jl_types::domain::option_wrapper::OptionWrapper;
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use yew_utils::vdom::comp_with;
|
use yew_utils::vdom::comp_with;
|
||||||
|
|
||||||
/// Give this component a list of options
|
/// Give this component a list of options
|
||||||
#[function_component(DropDown)]
|
#[function_component(DropDown)]
|
||||||
pub fn dropdown<T: Display + std::cmp::PartialEq + Clone + 'static>(props: &DropDownProps<T>) -> Html {
|
pub fn dropdown<T: Display + std::cmp::PartialEq + Clone + 'static>(
|
||||||
|
props: &DropDownProps<T>,
|
||||||
|
) -> Html {
|
||||||
let selection_changed_cb = {
|
let selection_changed_cb = {
|
||||||
let onchange_cb = props.onchange.clone();
|
let onchange_cb = props.onchange.clone();
|
||||||
let selected = props.selected.clone();
|
let selected = props.selected.clone();
|
||||||
@ -14,22 +16,29 @@ pub fn dropdown<T: Display + std::cmp::PartialEq + Clone + 'static>(props: &Drop
|
|||||||
selected.set(option.option.clone());
|
selected.set(option.option.clone());
|
||||||
match onchange_cb.clone() {
|
match onchange_cb.clone() {
|
||||||
Some(additional_cb) => additional_cb.emit(option),
|
Some(additional_cb) => additional_cb.emit(option),
|
||||||
None => {},
|
None => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
let drop_down = comp_with::<yew_utils::components::drop_down::DropDown<OptionWrapper<T>>>(yew_utils::components::drop_down::DropDownProps {
|
let drop_down = comp_with::<yew_utils::components::drop_down::DropDown<OptionWrapper<T>>>(
|
||||||
initial: {
|
yew_utils::components::drop_down::DropDownProps {
|
||||||
OptionWrapper::new((*props.selected).clone())},
|
initial: { OptionWrapper::new((*props.selected).clone()) },
|
||||||
options: {
|
options: {
|
||||||
let mut options: Vec<OptionWrapper<T>> = props.options.clone().into_iter().map(|option| OptionWrapper::new(Some(option))).collect();
|
let mut options: Vec<OptionWrapper<T>> = props
|
||||||
if props.has_none { options.insert(0, OptionWrapper::new(None)); }
|
.options
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.map(|option| OptionWrapper::new(Some(option)))
|
||||||
|
.collect();
|
||||||
|
if props.has_none {
|
||||||
|
options.insert(0, OptionWrapper::new(None));
|
||||||
|
}
|
||||||
options
|
options
|
||||||
},
|
},
|
||||||
selection_changed: selection_changed_cb.clone(),
|
selection_changed: selection_changed_cb.clone(),
|
||||||
class_css: Some("admin-dropdown".into())
|
class_css: Some("admin-dropdown".into()),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
//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}
|
||||||
@ -47,5 +56,5 @@ pub struct DropDownProps<T: Display + std::cmp::PartialEq + Clone> {
|
|||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
pub onchange: Option<Callback<OptionWrapper<T>>>,
|
pub onchange: Option<Callback<OptionWrapper<T>>>,
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
pub has_none: bool
|
pub has_none: bool,
|
||||||
}
|
}
|
@ -3,7 +3,7 @@ use yew::prelude::*;
|
|||||||
#[function_component(FeatureItem)]
|
#[function_component(FeatureItem)]
|
||||||
pub fn feature_item(props: &FeatureItemProps) -> Html {
|
pub fn feature_item(props: &FeatureItemProps) -> Html {
|
||||||
let props = props.clone();
|
let props = props.clone();
|
||||||
html!{
|
html! {
|
||||||
<div class={"details-body-feature-item"}>
|
<div class={"details-body-feature-item"}>
|
||||||
<div class={"details-body-feature-item-icon-container"}>
|
<div class={"details-body-feature-item-icon-container"}>
|
||||||
<i class={props.icon}></i>
|
<i class={props.icon}></i>
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
//TODO: Finish, add whatsapp link or email link?
|
|
||||||
#[function_component(FloatingWidget)]
|
#[function_component(FloatingWidget)]
|
||||||
pub fn floating_widget(props: &FloatingWidgetProps) -> Html {
|
pub fn floating_widget(props: &FloatingWidgetProps) -> Html {
|
||||||
let message = format!("Buenas, me interesa conocer mas sobre esta propiedad: %0A https://proyectosenconstruccion.com/details/{}", props.project_id);
|
let message = format!("Buenas, me interesa conocer mas sobre esta propiedad: %0A https://proyectosenconstruccion.com/details/{}", props.project_id);
|
||||||
|
129
src/components/media_picker.rs
Normal file
129
src/components/media_picker.rs
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
use jl_types::domain::media::{Media, MediaList};
|
||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
use crate::components::textfield::get_value_from_input_event;
|
||||||
|
|
||||||
|
#[function_component(MediaPicker)]
|
||||||
|
pub fn media_picker(props: &MediaPickerProps) -> Html {
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<div class={{"textfield-label-required"}}>{"Media del proyecto"}</div>
|
||||||
|
<div class={"mediapicker-container"}>
|
||||||
|
<MediaListRendered medialist={props.value.clone()} onchange={props.onchange.clone()}/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Properties, Clone)]
|
||||||
|
pub struct MediaPickerProps {
|
||||||
|
pub value: UseStateHandle<MediaList>,
|
||||||
|
pub onchange: Option<Callback<String>>,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub required: bool,
|
||||||
|
}
|
||||||
|
#[function_component(MediaListRendered)]
|
||||||
|
fn render_media_list(props: &MediaListProps) -> Html {
|
||||||
|
let mut media_elements = (*props.medialist).clone().media_list.into_iter().enumerate().map(|(index, media)| {
|
||||||
|
html! {
|
||||||
|
<div class={"mediapicker-media"}>
|
||||||
|
{
|
||||||
|
match media {
|
||||||
|
Media::Photo(url) => html! {
|
||||||
|
<img class={"mediapicker-media"} src={url.clone()} />
|
||||||
|
},
|
||||||
|
Media::Video(url) => html! {
|
||||||
|
<iframe class={"mediapicker-media"} src={format!("{url}?&mute=1")}></iframe>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<div class={"mediapicker-delete"} onclick={
|
||||||
|
let media_handle = props.medialist.clone();
|
||||||
|
let onchange_cb = props.onchange.clone();
|
||||||
|
Callback::from(move |_| {
|
||||||
|
match onchange_cb.clone() {
|
||||||
|
Some(cb) => cb.emit(String::new()),
|
||||||
|
None => {}
|
||||||
|
};
|
||||||
|
let mut media = (*media_handle).clone();
|
||||||
|
media.media_list.remove(index);
|
||||||
|
media_handle.set(media);
|
||||||
|
})
|
||||||
|
}>
|
||||||
|
<i class="fa-solid fa-trash-can"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}).collect::<Vec<Html>>();
|
||||||
|
let pressed = use_state(|| false);
|
||||||
|
let onclick = {
|
||||||
|
let pressed = pressed.clone();
|
||||||
|
Callback::from(move |event: MouseEvent| {
|
||||||
|
event.stop_propagation();
|
||||||
|
pressed.set(true);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
let video_str = use_state_eq(|| String::new());
|
||||||
|
let onvideo_input_changed = {
|
||||||
|
let video_str = video_str.clone();
|
||||||
|
Callback::from(move |e: InputEvent| {
|
||||||
|
let value = get_value_from_input_event(e);
|
||||||
|
video_str.set(value);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
let onvideo_keypress = {
|
||||||
|
let video_str = video_str.clone();
|
||||||
|
let media_handle = props.medialist.clone();
|
||||||
|
let onchange_cb = props.onchange.clone();
|
||||||
|
Callback::from(move |e: KeyboardEvent| {
|
||||||
|
if e.key() == String::from("Enter") {
|
||||||
|
match onchange_cb.clone() {
|
||||||
|
Some(cb) => cb.emit(String::new()),
|
||||||
|
None => {}
|
||||||
|
};
|
||||||
|
let mut media = (*media_handle).clone();
|
||||||
|
media.media_list.push(Media::Video((*video_str).clone()));
|
||||||
|
media_handle.set(media);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
let onphoto_upload = {
|
||||||
|
let media_handle = props.medialist.clone();
|
||||||
|
let onchange_cb = props.onchange.clone();
|
||||||
|
Callback::from(move |_: InputEvent| {
|
||||||
|
match onchange_cb.clone() {
|
||||||
|
Some(cb) => cb.emit(String::new()),
|
||||||
|
None => {}
|
||||||
|
};
|
||||||
|
//TODO: Upload picture, then push url into medialist
|
||||||
|
let mut media = (*media_handle).clone();
|
||||||
|
media.media_list.push(Media::Photo("".into()));
|
||||||
|
media_handle.set(media);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
media_elements.push(html! {
|
||||||
|
<div class={"mediapicker-new"} onclick={onclick}>
|
||||||
|
{if *pressed {
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<div class={"mediapicker-new-photo-section"}>
|
||||||
|
<input type={"file"} accept={"image/*"} oninput={onphoto_upload}/>
|
||||||
|
</div>
|
||||||
|
<div class={"mediapicker-new-video-section"}>
|
||||||
|
<input class={"mediapicker-new-video-section-input"} oninput={onvideo_input_changed} onkeypress={onvideo_keypress} placeholder={"Video de Youtube"}/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
html! {<i class="fa-solid fa-plus"></i>}
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
});
|
||||||
|
media_elements.into_iter().collect()
|
||||||
|
}
|
||||||
|
#[derive(Properties, PartialEq)]
|
||||||
|
pub struct MediaListProps {
|
||||||
|
pub medialist: UseStateHandle<MediaList>,
|
||||||
|
pub onchange: Option<Callback<String>>,
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
use jl_types::domain::media::Media;
|
use jl_types::domain::media::Media;
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
|
||||||
#[function_component(MediaSlideshow)]
|
#[function_component(MediaSlideshow)]
|
||||||
pub fn media_slideshow(props: &MediaSlideshowProps) -> Html {
|
pub fn media_slideshow(props: &MediaSlideshowProps) -> Html {
|
||||||
let current_media_index = use_state(|| 0);
|
let current_media_index = use_state(|| 0);
|
||||||
@ -11,7 +10,7 @@ pub fn media_slideshow(props: &MediaSlideshowProps) -> Html {
|
|||||||
let total_media_count = total_media_count.clone();
|
let total_media_count = total_media_count.clone();
|
||||||
Callback::from(move |_| {
|
Callback::from(move |_| {
|
||||||
if *current_media_index > 0 {
|
if *current_media_index > 0 {
|
||||||
current_media_index.set(*current_media_index -1);
|
current_media_index.set(*current_media_index - 1);
|
||||||
} else {
|
} else {
|
||||||
current_media_index.set(total_media_count - 1);
|
current_media_index.set(total_media_count - 1);
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
pub mod nav_bar;
|
|
||||||
pub mod project_card;
|
|
||||||
pub mod media_slideshow;
|
|
||||||
pub mod floating_widget;
|
|
||||||
pub mod feature;
|
|
||||||
pub mod footer;
|
|
||||||
pub mod agent_card;
|
|
||||||
pub mod admin_nav_bar;
|
pub mod admin_nav_bar;
|
||||||
pub mod admin_project;
|
pub mod admin_project;
|
||||||
pub mod textfield;
|
pub mod agent_card;
|
||||||
pub mod dropdown;
|
|
||||||
pub mod datepicker;
|
pub mod datepicker;
|
||||||
|
pub mod dropdown;
|
||||||
|
pub mod feature;
|
||||||
|
pub mod floating_widget;
|
||||||
|
pub mod footer;
|
||||||
|
pub mod media_picker;
|
||||||
|
pub mod media_slideshow;
|
||||||
|
pub mod nav_bar;
|
||||||
|
pub mod new_widget;
|
||||||
|
pub mod project_card;
|
||||||
|
pub mod textfield;
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
use stdweb::web::{IEventTarget, event::ResizeEvent};
|
use stdweb::web::{event::ResizeEvent, IEventTarget};
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use yew_router::prelude::{use_navigator, use_route};
|
use yew_router::prelude::{use_navigator, use_route};
|
||||||
|
|
||||||
use crate::{routes::main_router::{Route}, constants::{NAVBAR_COL_LANDING, NAVBAR_COL_CONTACTO, NAVBAR_COL_PROYECTOS, NAVBAR_COL_AGENTES}};
|
use crate::{
|
||||||
|
constants::{
|
||||||
|
NAVBAR_COL_AGENTES, NAVBAR_COL_CONTACTO, NAVBAR_COL_LANDING, NAVBAR_COL_PROYECTOS,
|
||||||
|
},
|
||||||
|
routes::main_router::Route,
|
||||||
|
};
|
||||||
//use yew_router::prelude::use_navigator;
|
//use yew_router::prelude::use_navigator;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[function_component(NavigationBar)]
|
#[function_component(NavigationBar)]
|
||||||
pub fn navigation_bar() -> Html {
|
pub fn navigation_bar() -> Html {
|
||||||
let current_route: Option<Route> = use_route();
|
let current_route: Option<Route> = use_route();
|
||||||
@ -23,10 +26,12 @@ pub fn navigation_bar() -> Html {
|
|||||||
let navbar_toggle = navbar_toggle.clone();
|
let navbar_toggle = navbar_toggle.clone();
|
||||||
Callback::from(move |_| navbar_toggle.set(!*navbar_toggle))
|
Callback::from(move |_| navbar_toggle.set(!*navbar_toggle))
|
||||||
};
|
};
|
||||||
let window_device_handle = use_state_eq(|| if stdweb::web::window().inner_width() > 750 {
|
let window_device_handle = use_state_eq(|| {
|
||||||
|
if stdweb::web::window().inner_width() > 750 {
|
||||||
WindowDevice::Desktop
|
WindowDevice::Desktop
|
||||||
} else {
|
} else {
|
||||||
WindowDevice::Mobile
|
WindowDevice::Mobile
|
||||||
|
}
|
||||||
});
|
});
|
||||||
let window_device_handle_cloned = window_device_handle.clone();
|
let window_device_handle_cloned = window_device_handle.clone();
|
||||||
stdweb::web::window().add_event_listener(move |_: ResizeEvent| {
|
stdweb::web::window().add_event_listener(move |_: ResizeEvent| {
|
||||||
@ -38,7 +43,6 @@ pub fn navigation_bar() -> Html {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<div class={"navbar-background"}>
|
<div class={"navbar-background"}>
|
||||||
<div class={"navbar-container"}>
|
<div class={"navbar-container"}>
|
||||||
@ -117,5 +121,5 @@ pub fn navigation_bar() -> Html {
|
|||||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||||
enum WindowDevice {
|
enum WindowDevice {
|
||||||
Desktop,
|
Desktop,
|
||||||
Mobile
|
Mobile,
|
||||||
}
|
}
|
34
src/components/new_widget.rs
Normal file
34
src/components/new_widget.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
use yew::prelude::*;
|
||||||
|
use yew_router::prelude::use_navigator;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
pages::admin::edit::{EditItem, EditType},
|
||||||
|
routes::main_router::Route,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[function_component(NewThingWidget)]
|
||||||
|
pub fn new_thing_widget(props: &NewThingWidgetProps) -> Html {
|
||||||
|
let navigator = use_navigator().unwrap();
|
||||||
|
let onclick = {
|
||||||
|
let edit_item = props.item.clone();
|
||||||
|
let navigator = navigator.clone();
|
||||||
|
Callback::from(move |_| {
|
||||||
|
navigator.push(&Route::AdminEdit {
|
||||||
|
edit_type: EditType::New,
|
||||||
|
edit_item: edit_item.clone(),
|
||||||
|
});
|
||||||
|
})
|
||||||
|
};
|
||||||
|
html! {
|
||||||
|
<a class={"floating-widget-container"} style={"background-color: #02114A;"} onclick={onclick}>
|
||||||
|
<div class={"floating-widget-logo"}>
|
||||||
|
<i class="fa-solid fa-plus"></i>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Properties, PartialEq)]
|
||||||
|
pub struct NewThingWidgetProps {
|
||||||
|
pub item: EditItem,
|
||||||
|
}
|
@ -1,16 +1,15 @@
|
|||||||
use jl_types::{domain::{media::Media}, dto::project_card::ProjectCardDto};
|
use jl_types::{domain::media::Media, dto::project_card::ProjectCardDto};
|
||||||
use thousands::Separable;
|
use thousands::Separable;
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use yew_router::prelude::use_navigator;
|
use yew_router::prelude::use_navigator;
|
||||||
|
|
||||||
use crate::routes::main_router::Route;
|
use crate::routes::main_router::Route;
|
||||||
|
|
||||||
|
|
||||||
#[function_component(ProjectCard)]
|
#[function_component(ProjectCard)]
|
||||||
pub fn project_card(props: &ProjectCardProps) -> 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_id = props.project.id.clone();
|
||||||
let project_view_cb = Callback::from(move |_|{
|
let project_view_cb = Callback::from(move |_| {
|
||||||
navigator.push(&Route::Details { project_id });
|
navigator.push(&Route::Details { project_id });
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -25,9 +24,14 @@ pub fn project_card(props: &ProjectCardProps) -> Html {
|
|||||||
cover_image_url = String::new()
|
cover_image_url = String::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
let project_title = format!("{} en {}, {}", props.project.project_type, props.project.district, props.project.city);
|
let project_title = format!(
|
||||||
|
"{} en {}, {}",
|
||||||
|
props.project.project_type, props.project.district, props.project.city
|
||||||
|
);
|
||||||
|
|
||||||
let project_price = format!("Desde US${}", match props.project.starts_from {
|
let project_price = format!(
|
||||||
|
"Desde US${}",
|
||||||
|
match props.project.starts_from {
|
||||||
Some(price) => {
|
Some(price) => {
|
||||||
let price_separated = price.separate_with_commas();
|
let price_separated = price.separate_with_commas();
|
||||||
if price_separated.contains(".") {
|
if price_separated.contains(".") {
|
||||||
@ -35,17 +39,17 @@ pub fn project_card(props: &ProjectCardProps) -> Html {
|
|||||||
} else {
|
} else {
|
||||||
format!("{price_separated}.00")
|
format!("{price_separated}.00")
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
None => "0.00".into()
|
None => "0.00".into(),
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
let project_condition = props.project.project_condition.to_string();
|
let project_condition = props.project.project_condition.to_string();
|
||||||
|
|
||||||
let project_est_finish_date = props.project.finish_date.format("%m/%Y");
|
let project_est_finish_date = props.project.finish_date.format("%m/%Y");
|
||||||
//let project_location_written;
|
//let project_location_written;
|
||||||
|
|
||||||
|
html! {
|
||||||
html!{
|
|
||||||
<div class={"project-listing-card"} onclick={project_view_cb}>
|
<div class={"project-listing-card"} onclick={project_view_cb}>
|
||||||
<img src={cover_image_url} alt={"project image"} class={"project-search-result-card-picture"}/>
|
<img src={cover_image_url} alt={"project image"} class={"project-search-result-card-picture"}/>
|
||||||
|
|
||||||
@ -71,5 +75,5 @@ pub fn project_card(props: &ProjectCardProps) -> Html {
|
|||||||
|
|
||||||
#[derive(Properties, PartialEq, PartialOrd)]
|
#[derive(Properties, PartialEq, PartialOrd)]
|
||||||
pub struct ProjectCardProps {
|
pub struct ProjectCardProps {
|
||||||
pub project: ProjectCardDto
|
pub project: ProjectCardDto,
|
||||||
}
|
}
|
@ -3,7 +3,6 @@ use wasm_bindgen::{JsCast, UnwrapThrowExt};
|
|||||||
use web_sys::{HtmlInputElement, HtmlTextAreaElement};
|
use web_sys::{HtmlInputElement, HtmlTextAreaElement};
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
|
||||||
/// This component is a text
|
/// This component is a text
|
||||||
#[function_component(TextField)]
|
#[function_component(TextField)]
|
||||||
pub fn textfield(props: &TextFieldProps) -> Html {
|
pub fn textfield(props: &TextFieldProps) -> Html {
|
||||||
@ -11,24 +10,26 @@ pub fn textfield(props: &TextFieldProps) -> Html {
|
|||||||
let handle = props.value.clone();
|
let handle = props.value.clone();
|
||||||
let fieldtype = props.fieldtype.clone();
|
let fieldtype = props.fieldtype.clone();
|
||||||
let onchange = props.onchange.clone();
|
let onchange = props.onchange.clone();
|
||||||
Callback::from(move |e: InputEvent| {
|
Callback::from(move |e: InputEvent| match fieldtype {
|
||||||
match fieldtype {
|
|
||||||
TextFieldType::Input => {
|
TextFieldType::Input => {
|
||||||
let value = get_value_from_input_event(e);
|
let value = get_value_from_input_event(e);
|
||||||
match onchange.clone() {
|
match onchange.clone() {
|
||||||
Some(onchange) => { onchange.emit(value.clone()); },
|
Some(onchange) => {
|
||||||
|
onchange.emit(value.clone());
|
||||||
|
}
|
||||||
None => {}
|
None => {}
|
||||||
};
|
};
|
||||||
handle.set(value);
|
handle.set(value);
|
||||||
},
|
}
|
||||||
TextFieldType::TextArea => {
|
TextFieldType::TextArea => {
|
||||||
let value = get_value_from_input_event(e);
|
let value = get_value_from_textarea_event(e);
|
||||||
match onchange.clone() {
|
match onchange.clone() {
|
||||||
Some(onchange) => { onchange.emit(value.clone()); },
|
Some(onchange) => {
|
||||||
|
onchange.emit(value.clone());
|
||||||
|
}
|
||||||
None => {}
|
None => {}
|
||||||
};
|
};
|
||||||
handle.set(value);
|
handle.set(value);
|
||||||
},
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
@ -55,7 +56,7 @@ pub struct TextFieldProps {
|
|||||||
pub fieldtype: TextFieldType,
|
pub fieldtype: TextFieldType,
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
pub required: bool,
|
pub required: bool,
|
||||||
pub onchange: Option<Callback<String>>
|
pub onchange: Option<Callback<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Default)]
|
#[derive(PartialEq, Clone, Default)]
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
use crate::{components::admin_nav_bar::AdminNavigationBar, api::backend::get_all_agents};
|
use crate::{api::backend::get_all_agents, components::admin_nav_bar::AdminNavigationBar};
|
||||||
|
|
||||||
#[function_component(AdminAgents)]
|
#[function_component(AdminAgents)]
|
||||||
pub fn admin_agents() -> Html {
|
pub fn admin_agents() -> Html {
|
||||||
|
|
||||||
let agents = use_state(|| Vec::new());
|
let agents = use_state(|| Vec::new());
|
||||||
|
|
||||||
use_state(|| {
|
use_state(|| {
|
||||||
@ -13,7 +12,7 @@ pub fn admin_agents() -> Html {
|
|||||||
match get_all_agents().await {
|
match get_all_agents().await {
|
||||||
Ok(agents) => {
|
Ok(agents) => {
|
||||||
agents_handle.set(agents);
|
agents_handle.set(agents);
|
||||||
},
|
}
|
||||||
Err(error) => log::error!("Error retrieving agents from backend. Error: {}", error),
|
Err(error) => log::error!("Error retrieving agents from backend. Error: {}", error),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
use crate::{components::admin_nav_bar::AdminNavigationBar, api::backend::get_all_contacts};
|
use crate::{api::backend::get_all_contacts, components::admin_nav_bar::AdminNavigationBar};
|
||||||
|
|
||||||
#[function_component(AdminContacts)]
|
#[function_component(AdminContacts)]
|
||||||
pub fn admin_contacts() -> Html {
|
pub fn admin_contacts() -> Html {
|
||||||
@ -12,8 +12,10 @@ pub fn admin_contacts() -> Html {
|
|||||||
match get_all_contacts().await {
|
match get_all_contacts().await {
|
||||||
Ok(contacts) => {
|
Ok(contacts) => {
|
||||||
contacts_handle.set(contacts);
|
contacts_handle.set(contacts);
|
||||||
},
|
}
|
||||||
Err(error) => log::error!("Error retrieving contacts from backend. Error: {}", error),
|
Err(error) => {
|
||||||
|
log::error!("Error retrieving contacts from backend. Error: {}", error)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,11 +1,26 @@
|
|||||||
use std::{fmt::Display, str::FromStr};
|
use std::{fmt::Display, str::FromStr};
|
||||||
|
|
||||||
use jl_types::{ domain::{agent::Agent, media::{MediaList}, unit::Unit, project_state::ProjectState, project_condition::ProjectCondition, project_type::ProjectType}, dto::listing::Listing};
|
use jl_types::{
|
||||||
|
domain::{
|
||||||
|
agent::Agent, media::MediaList, project_condition::ProjectCondition,
|
||||||
|
project_state::ProjectState, project_type::ProjectType, unit::Unit,
|
||||||
|
},
|
||||||
|
dto::listing::Listing,
|
||||||
|
};
|
||||||
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::{components::{admin_nav_bar::AdminNavigationBar, textfield::{TextField, TextFieldType}, dropdown::DropDown, datepicker::DatePicker}, api::backend::{get_project_listing, get_all_agents}};
|
use crate::{
|
||||||
|
api::backend::{get_all_agents, get_project_listing},
|
||||||
|
components::{
|
||||||
|
admin_nav_bar::AdminNavigationBar,
|
||||||
|
datepicker::DatePicker,
|
||||||
|
dropdown::DropDown,
|
||||||
|
media_picker::MediaPicker,
|
||||||
|
textfield::{TextField, TextFieldType},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
/// 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.
|
||||||
#[function_component(AdminEditPage)]
|
#[function_component(AdminEditPage)]
|
||||||
@ -15,34 +30,38 @@ pub fn edit_page(props: &AdminEditPageProps) -> Html {
|
|||||||
use_state(|| {
|
use_state(|| {
|
||||||
let listing = listing.clone();
|
let listing = listing.clone();
|
||||||
match props.edit_item {
|
match props.edit_item {
|
||||||
EditItem::Agent => {
|
EditItem::Agent => {}
|
||||||
|
|
||||||
},
|
|
||||||
EditItem::Project => {
|
EditItem::Project => {
|
||||||
match props.edit_type {
|
match props.edit_type {
|
||||||
EditType::New => {},
|
EditType::New => {}
|
||||||
EditType::Existing(uid) => {
|
EditType::Existing(uid) => {
|
||||||
wasm_bindgen_futures::spawn_local(async move {
|
wasm_bindgen_futures::spawn_local(async move {
|
||||||
let listing_result = get_project_listing(&uid).await;
|
let listing_result = get_project_listing(&uid).await;
|
||||||
match listing_result {
|
match listing_result {
|
||||||
Ok(listing_persisted) => {
|
Ok(listing_persisted) => {
|
||||||
listing.set(Some(listing_persisted));
|
listing.set(Some(listing_persisted));
|
||||||
},
|
}
|
||||||
Err(error) => log::error!("Error loading listing: {error}")
|
Err(error) => log::error!("Error loading listing: {error}"),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
EditItem::Unit(_) => {},
|
EditItem::Unit(_) => {}
|
||||||
}});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<>
|
<>
|
||||||
<AdminNavigationBar/>
|
<AdminNavigationBar/>
|
||||||
<div class={"admin-page-container"}>
|
<div class={"admin-page-container"}>
|
||||||
<div class={"admin-start-container"}>
|
<div class={"admin-start-container"}>
|
||||||
<div class={"admin-panel-page-title"}>{ format!("Editar {}", match props.edit_item {
|
<div class={"admin-panel-page-title"}>{ format!("{} {}",
|
||||||
|
match props.edit_type {
|
||||||
|
EditType::New => "Agregar",
|
||||||
|
EditType::Existing(_) => "Editar",
|
||||||
|
},
|
||||||
|
match props.edit_item {
|
||||||
EditItem::Agent => "Agente",
|
EditItem::Agent => "Agente",
|
||||||
EditItem::Project => "Proyecto",
|
EditItem::Project => "Proyecto",
|
||||||
EditItem::Unit(_) => "Unidad",
|
EditItem::Unit(_) => "Unidad",
|
||||||
@ -61,7 +80,6 @@ pub fn edit_page(props: &AdminEditPageProps) -> Html {
|
|||||||
</>
|
</>
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone)]
|
#[derive(PartialEq, Clone)]
|
||||||
@ -73,7 +91,8 @@ pub enum EditItem {
|
|||||||
}
|
}
|
||||||
#[derive(PartialEq, Clone)]
|
#[derive(PartialEq, Clone)]
|
||||||
pub enum EditType {
|
pub enum EditType {
|
||||||
New, Existing(Uuid)
|
New,
|
||||||
|
Existing(Uuid),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Properties, PartialEq)]
|
#[derive(Properties, PartialEq)]
|
||||||
@ -103,7 +122,9 @@ pub fn generate_fields_for_project(props: &ProjectFieldsProps) -> Html {
|
|||||||
let project_admin_tag: UseStateHandle<String> = use_state_eq(|| String::new());
|
let project_admin_tag: UseStateHandle<String> = use_state_eq(|| String::new());
|
||||||
let project_finish_date = use_state_eq(|| None);
|
let project_finish_date = use_state_eq(|| None);
|
||||||
let project_floors = use_state_eq(|| String::new());
|
let project_floors = use_state_eq(|| String::new());
|
||||||
let media: UseStateHandle<MediaList> = use_state_eq(|| MediaList { media_list: Vec::new() });
|
let media: UseStateHandle<MediaList> = use_state_eq(|| MediaList {
|
||||||
|
media_list: Vec::new(),
|
||||||
|
});
|
||||||
let units: UseStateHandle<Vec<Unit>> = use_state_eq(|| Vec::new());
|
let units: UseStateHandle<Vec<Unit>> = use_state_eq(|| Vec::new());
|
||||||
|
|
||||||
let ontype_cb = {
|
let ontype_cb = {
|
||||||
@ -112,6 +133,12 @@ pub fn generate_fields_for_project(props: &ProjectFieldsProps) -> Html {
|
|||||||
user_typed.set(true);
|
user_typed.set(true);
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
let ontypedate_cb = {
|
||||||
|
let user_typed = user_typed.clone();
|
||||||
|
Callback::from(move |_| {
|
||||||
|
user_typed.set(true);
|
||||||
|
})
|
||||||
|
};
|
||||||
let onselect_cb = {
|
let onselect_cb = {
|
||||||
let user_typed = user_typed.clone();
|
let user_typed = user_typed.clone();
|
||||||
Callback::from(move |_| {
|
Callback::from(move |_| {
|
||||||
@ -144,7 +171,9 @@ pub fn generate_fields_for_project(props: &ProjectFieldsProps) -> Html {
|
|||||||
wasm_bindgen_futures::spawn_local(async move {
|
wasm_bindgen_futures::spawn_local(async move {
|
||||||
match get_all_agents().await {
|
match get_all_agents().await {
|
||||||
Ok(persisted_agents) => all_agents.set(persisted_agents),
|
Ok(persisted_agents) => all_agents.set(persisted_agents),
|
||||||
Err(error) => log::error!("Error fetching agents from admin panel edit screen: {error}"),
|
Err(error) => {
|
||||||
|
log::error!("Error fetching agents from admin panel edit screen: {error}")
|
||||||
|
}
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@ -187,13 +216,27 @@ pub fn generate_fields_for_project(props: &ProjectFieldsProps) -> Html {
|
|||||||
Some(listing) => Some(listing.project.finish_date.date()),
|
Some(listing) => Some(listing.project.finish_date.date()),
|
||||||
None => None,
|
None => None,
|
||||||
});
|
});
|
||||||
|
media.set(match listing_opt.clone() {
|
||||||
|
Some(listing) => listing.project.media,
|
||||||
|
None => MediaList {
|
||||||
|
media_list: Vec::new(),
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<>
|
<>
|
||||||
<TextField label={"Ciudad"} value={location_city} required={true} onchange={ontype_cb.clone()}/>
|
<TextField label={"Ciudad"} value={location_city} required={true} onchange={ontype_cb.clone()}/>
|
||||||
<TextField label={"Distrito"} value={location_district} required={true} onchange={ontype_cb.clone()} />
|
<TextField label={"Distrito"} value={location_district} required={true} onchange={ontype_cb.clone()} />
|
||||||
{if (*agent).clone().is_none() { html! { } } else {
|
<MediaPicker value={media} onchange={ontype_cb.clone()}/>
|
||||||
|
{if (*agent).clone().is_none() {
|
||||||
|
html! {
|
||||||
|
<div class={"textfield-container"}>
|
||||||
|
<div class={"textfield-label-required"}>{"Agente asignado"}</div>
|
||||||
|
<DropDown<Agent> options={(*all_agents).clone()} selected={agent} onchange={onselect_cb.clone()} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
} else {
|
||||||
html! {
|
html! {
|
||||||
<div class={"textfield-container"}>
|
<div class={"textfield-container"}>
|
||||||
<div class={"textfield-label-required"}>{"Agente asignado"}</div>
|
<div class={"textfield-label-required"}>{"Agente asignado"}</div>
|
||||||
@ -202,7 +245,14 @@ pub fn generate_fields_for_project(props: &ProjectFieldsProps) -> Html {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{if (*project_state).clone().is_none() { html! { } } else {
|
{if (*project_state).clone().is_none() {
|
||||||
|
html! {
|
||||||
|
<div class={"textfield-container"}>
|
||||||
|
<div class={"textfield-label-required"}>{"Estado del Proyecto"}</div>
|
||||||
|
<DropDown<ProjectState> options={vec![ProjectState::Finished, ProjectState::InConstruction]} selected={project_state} onchange={onselect_cb2} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
} else {
|
||||||
html! {
|
html! {
|
||||||
<div class={"textfield-container"}>
|
<div class={"textfield-container"}>
|
||||||
<div class={"textfield-label-required"}>{"Estado del Proyecto"}</div>
|
<div class={"textfield-label-required"}>{"Estado del Proyecto"}</div>
|
||||||
@ -212,7 +262,14 @@ pub fn generate_fields_for_project(props: &ProjectFieldsProps) -> Html {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{if (*project_condition).clone().is_none() { html! { } } else {
|
{if (*project_condition).clone().is_none() {
|
||||||
|
html! {
|
||||||
|
<div class={"textfield-container"}>
|
||||||
|
<div class={"textfield-label-required"}>{"Condición del Proyecto"}</div>
|
||||||
|
<DropDown<ProjectCondition> options={vec![ProjectCondition::Resale, ProjectCondition::New]} selected={project_condition} onchange={onselect_cb3} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
} else {
|
||||||
html! {
|
html! {
|
||||||
<div class={"textfield-container"}>
|
<div class={"textfield-container"}>
|
||||||
<div class={"textfield-label-required"}>{"Condición del Proyecto"}</div>
|
<div class={"textfield-label-required"}>{"Condición del Proyecto"}</div>
|
||||||
@ -221,7 +278,14 @@ pub fn generate_fields_for_project(props: &ProjectFieldsProps) -> Html {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{if (*project_type).clone().is_none() { html! { } } else {
|
{if (*project_type).clone().is_none() {
|
||||||
|
html! {
|
||||||
|
<div class={"textfield-container"}>
|
||||||
|
<div class={"textfield-label-required"}>{"Tipo de Proyecto"}</div>
|
||||||
|
<DropDown<ProjectType> options={vec![ProjectType::Apartamento, ProjectType::Casa, ProjectType::Oficina, ProjectType::Local, ProjectType::Solar]} selected={project_type} onchange={onselect_cb4} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
} else {
|
||||||
html! {
|
html! {
|
||||||
<div class={"textfield-container"}>
|
<div class={"textfield-container"}>
|
||||||
<div class={"textfield-label-required"}>{"Tipo de Proyecto"}</div>
|
<div class={"textfield-label-required"}>{"Tipo de Proyecto"}</div>
|
||||||
@ -230,10 +294,10 @@ pub fn generate_fields_for_project(props: &ProjectFieldsProps) -> Html {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
<TextField label={"Pisos"} value={project_floors} required={true} />
|
<TextField label={"Pisos"} value={project_floors} required={true} onchange={ontype_cb.clone()}/>
|
||||||
<DatePicker label={"Fecha de entrega Est."} value={project_finish_date} required={true} />
|
<DatePicker label={"Fecha de entrega Est."} value={project_finish_date} required={true} onchange={ontypedate_cb.clone()}/>
|
||||||
<TextField label={"Descripción"} value={project_description} fieldtype={TextFieldType::TextArea} />
|
<TextField label={"Descripción"} value={project_description} fieldtype={TextFieldType::TextArea} onchange={ontype_cb.clone()}/>
|
||||||
<TextField label={"Comentario interno"} value={project_admin_tag} />
|
<TextField label={"Comentario interno"} value={project_admin_tag} onchange={ontype_cb.clone()}/>
|
||||||
|
|
||||||
<div class={"admin-edit-submit-button"}>
|
<div class={"admin-edit-submit-button"}>
|
||||||
{"Actualizar"}
|
{"Actualizar"}
|
||||||
@ -258,7 +322,7 @@ impl Display for EditType {
|
|||||||
EditType::Existing(id) => {
|
EditType::Existing(id) => {
|
||||||
let fmt_id = id.to_string().replace("-", "");
|
let fmt_id = id.to_string().replace("-", "");
|
||||||
write!(f, "existing{fmt_id}")
|
write!(f, "existing{fmt_id}")
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -276,7 +340,7 @@ impl FromStr for EditItem {
|
|||||||
match s {
|
match s {
|
||||||
"agent" => Ok(Self::Agent),
|
"agent" => Ok(Self::Agent),
|
||||||
"project" => Ok(Self::Project),
|
"project" => Ok(Self::Project),
|
||||||
_ => Err(())
|
_ => Err(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -295,7 +359,7 @@ impl FromStr for EditType {
|
|||||||
}
|
}
|
||||||
match s {
|
match s {
|
||||||
"new" => Ok(Self::New),
|
"new" => Ok(Self::New),
|
||||||
_ => Err(())
|
_ => Err(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use yew_router::prelude::use_navigator;
|
use yew_router::prelude::use_navigator;
|
||||||
|
|
||||||
use crate::{utils::input::get_value_from_input_event, routes::main_router::Route};
|
use crate::{routes::main_router::Route, utils::input::get_value_from_input_event};
|
||||||
|
|
||||||
#[function_component(AdminLoginPage)]
|
#[function_component(AdminLoginPage)]
|
||||||
pub fn login_page() -> Html {
|
pub fn login_page() -> Html {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
pub mod login;
|
|
||||||
pub mod start;
|
|
||||||
pub mod projects;
|
|
||||||
pub mod contacts;
|
|
||||||
pub mod agents;
|
pub mod agents;
|
||||||
|
pub mod contacts;
|
||||||
pub mod edit;
|
pub mod edit;
|
||||||
|
pub mod login;
|
||||||
|
pub mod projects;
|
||||||
|
pub mod start;
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
use crate::{components::{admin_nav_bar::AdminNavigationBar, admin_project::AdminProject}, api::backend::get_all_projects_with_filters_paged};
|
use crate::{
|
||||||
|
api::backend::get_all_projects_with_filters_paged,
|
||||||
|
components::{
|
||||||
|
admin_nav_bar::AdminNavigationBar, admin_project::AdminProject, new_widget::NewThingWidget,
|
||||||
|
},
|
||||||
|
pages::admin::edit::EditItem,
|
||||||
|
};
|
||||||
|
|
||||||
#[function_component(AdminProjects)]
|
#[function_component(AdminProjects)]
|
||||||
pub fn admin_projects() -> Html {
|
pub fn admin_projects() -> Html {
|
||||||
|
|
||||||
let projects = use_state(|| Vec::new());
|
let projects = use_state(|| Vec::new());
|
||||||
|
|
||||||
use_state(|| {
|
use_state(|| {
|
||||||
@ -13,17 +18,18 @@ pub fn admin_projects() -> Html {
|
|||||||
match get_all_projects_with_filters_paged(&1, Vec::new()).await {
|
match get_all_projects_with_filters_paged(&1, Vec::new()).await {
|
||||||
Ok(projects) => {
|
Ok(projects) => {
|
||||||
projects_handle.set(projects);
|
projects_handle.set(projects);
|
||||||
},
|
}
|
||||||
Err(error) => log::error!("Error retrieving projects from backend. Error: {}", error),
|
Err(error) => {
|
||||||
|
log::error!("Error retrieving projects from backend. Error: {}", error)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<>
|
<>
|
||||||
<AdminNavigationBar/>
|
<AdminNavigationBar/>
|
||||||
|
<NewThingWidget item={EditItem::Project}/>
|
||||||
<div class={"admin-page-container"}>
|
<div class={"admin-page-container"}>
|
||||||
<div class={"admin-start-container"}>
|
<div class={"admin-start-container"}>
|
||||||
<div class={"admin-panel-page-title"}>{"Proyectos"}</div>
|
<div class={"admin-panel-page-title"}>{"Proyectos"}</div>
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
use jl_types::domain::agent::Agent;
|
use jl_types::domain::agent::Agent;
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
use crate::{components::{nav_bar::NavigationBar, footer::PageFooter, agent_card::AgentCard}, api::backend::get_all_agents};
|
use crate::{
|
||||||
|
api::backend::get_all_agents,
|
||||||
|
components::{agent_card::AgentCard, footer::PageFooter, nav_bar::NavigationBar},
|
||||||
|
};
|
||||||
|
|
||||||
#[function_component(AgentsPage)]
|
#[function_component(AgentsPage)]
|
||||||
pub fn agents_page() -> Html {
|
pub fn agents_page() -> Html {
|
||||||
@ -14,15 +16,17 @@ pub fn agents_page() -> Html {
|
|||||||
wasm_bindgen_futures::spawn_local(async move {
|
wasm_bindgen_futures::spawn_local(async move {
|
||||||
match get_all_agents().await {
|
match get_all_agents().await {
|
||||||
Ok(mut fetched_agents) => {
|
Ok(mut fetched_agents) => {
|
||||||
if let Some(jl_agent_pos) = fetched_agents.iter().position(|agent| agent.full_name == "Jorge Ledesma") {
|
if let Some(jl_agent_pos) = fetched_agents
|
||||||
|
.iter()
|
||||||
|
.position(|agent| agent.full_name == "Jorge Ledesma")
|
||||||
|
{
|
||||||
let removed_agent = fetched_agents.remove(jl_agent_pos);
|
let removed_agent = fetched_agents.remove(jl_agent_pos);
|
||||||
fetched_agents.insert(0, removed_agent);
|
fetched_agents.insert(0, removed_agent);
|
||||||
}
|
}
|
||||||
agents.set(fetched_agents);
|
agents.set(fetched_agents);
|
||||||
finished_loading.set(true);
|
finished_loading.set(true);
|
||||||
},
|
}
|
||||||
Err(error) => log::error!("Error fetching listing. Error: {:?}", error)
|
Err(error) => log::error!("Error fetching listing. Error: {:?}", error),
|
||||||
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -2,12 +2,15 @@ use jl_types::dto::payloads::contact::ContactPayload;
|
|||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use yew_router::prelude::use_navigator;
|
use yew_router::prelude::use_navigator;
|
||||||
|
|
||||||
use crate::{components::{nav_bar::NavigationBar, footer::PageFooter}, utils::input::{get_value_from_input_event, get_value_from_textarea_event}, api::backend::create_new_contact_request, routes::main_router::Route};
|
use crate::{
|
||||||
|
api::backend::create_new_contact_request,
|
||||||
|
components::{footer::PageFooter, nav_bar::NavigationBar},
|
||||||
|
routes::main_router::Route,
|
||||||
|
utils::input::{get_value_from_input_event, get_value_from_textarea_event},
|
||||||
|
};
|
||||||
|
|
||||||
#[function_component(ContactPage)]
|
#[function_component(ContactPage)]
|
||||||
pub fn contact_page() -> Html {
|
pub fn contact_page() -> Html {
|
||||||
|
|
||||||
let navigator = use_navigator().unwrap();
|
let navigator = use_navigator().unwrap();
|
||||||
|
|
||||||
let first_name = use_state(|| String::new());
|
let first_name = use_state(|| String::new());
|
||||||
@ -50,17 +53,23 @@ pub fn contact_page() -> Html {
|
|||||||
first_name: (*first_name).clone(),
|
first_name: (*first_name).clone(),
|
||||||
last_name: (*last_name).clone(),
|
last_name: (*last_name).clone(),
|
||||||
credential: (*credential).clone(),
|
credential: (*credential).clone(),
|
||||||
message: (*messsage).clone()
|
message: (*messsage).clone(),
|
||||||
};
|
};
|
||||||
let navigator = navigator.clone();
|
let navigator = navigator.clone();
|
||||||
if contact.first_name.len() > 1 && contact.last_name.len() > 1 && contact.credential.len() > 3 && contact.message.len() > 5 {
|
if contact.first_name.len() > 1
|
||||||
|
&& contact.last_name.len() > 1
|
||||||
|
&& contact.credential.len() > 3
|
||||||
|
&& contact.message.len() > 5
|
||||||
|
{
|
||||||
wasm_bindgen_futures::spawn_local(async move {
|
wasm_bindgen_futures::spawn_local(async move {
|
||||||
match create_new_contact_request(contact).await {
|
match create_new_contact_request(contact).await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
// TODO: Take to homepage? Say thanks?
|
// TODO: Take to homepage? Say thanks?
|
||||||
navigator.push(&Route::LandingPage);
|
navigator.push(&Route::LandingPage);
|
||||||
},
|
}
|
||||||
Err(error) => log::error!("Error in sending a contact request to the backend: {error}")
|
Err(error) => log::error!(
|
||||||
|
"Error in sending a contact request to the backend: {error}"
|
||||||
|
),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,21 @@
|
|||||||
use jl_types::{dto::listing::Listing, domain::{unit_type::UnitType, unit::Unit, agent::Agent}};
|
use jl_types::{
|
||||||
use log::{error};
|
domain::{agent::Agent, unit::Unit, unit_type::UnitType},
|
||||||
|
dto::listing::Listing,
|
||||||
|
};
|
||||||
|
use log::error;
|
||||||
use thousands::Separable;
|
use thousands::Separable;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use yew_router::{prelude::{use_location}};
|
use yew_router::prelude::use_location;
|
||||||
|
|
||||||
use crate::{components::{nav_bar::NavigationBar, media_slideshow::MediaSlideshow, floating_widget::FloatingWidget, feature::FeatureItem, footer::PageFooter}, api::backend::{get_project_listing, get_agent_with_shortcode}, utils::storage::{self, StorageKey}};
|
use crate::{
|
||||||
|
api::backend::{get_agent_with_shortcode, get_project_listing},
|
||||||
|
components::{
|
||||||
|
feature::FeatureItem, floating_widget::FloatingWidget, footer::PageFooter,
|
||||||
|
media_slideshow::MediaSlideshow, nav_bar::NavigationBar,
|
||||||
|
},
|
||||||
|
utils::storage::{self, StorageKey},
|
||||||
|
};
|
||||||
|
|
||||||
const ALPHABET_LETTERS: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
const ALPHABET_LETTERS: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
|
|
||||||
@ -21,8 +31,10 @@ pub fn details_page(props: &DetailsPageProps) -> Html {
|
|||||||
if query_param.0 == String::from("shortcode") {
|
if query_param.0 == String::from("shortcode") {
|
||||||
// Store shortcode in localstorage
|
// Store shortcode in localstorage
|
||||||
match storage::store_in_local_storage(StorageKey::AgentShortcode, &query_param.1) {
|
match storage::store_in_local_storage(StorageKey::AgentShortcode, &query_param.1) {
|
||||||
Ok(_) => {},
|
Ok(_) => {}
|
||||||
Err(error) => log::error!("Error storing agent shortcode in localstorage\n: {}", error),
|
Err(error) => {
|
||||||
|
log::error!("Error storing agent shortcode in localstorage\n: {}", error)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -36,15 +48,20 @@ pub fn details_page(props: &DetailsPageProps) -> Html {
|
|||||||
let override_agent_handle = override_agent_handle.clone();
|
let override_agent_handle = override_agent_handle.clone();
|
||||||
wasm_bindgen_futures::spawn_local(async move {
|
wasm_bindgen_futures::spawn_local(async move {
|
||||||
match get_agent_with_shortcode(&persisted_shortcode).await {
|
match get_agent_with_shortcode(&persisted_shortcode).await {
|
||||||
Ok(agent) => { override_agent_handle.set(Some(agent)) },
|
Ok(agent) => override_agent_handle.set(Some(agent)),
|
||||||
Err(error) => error!("Error fetching agent with shortcode. Error: {:?}", error),
|
Err(error) => {
|
||||||
|
error!("Error fetching agent with shortcode. Error: {:?}", error)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
},
|
}
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(error) => log::error!("Error reading agent shortcode from localstorage\n: {}", error),
|
Err(error) => log::error!(
|
||||||
|
"Error reading agent shortcode from localstorage\n: {}",
|
||||||
|
error
|
||||||
|
),
|
||||||
};
|
};
|
||||||
// Project loading part
|
// Project loading part
|
||||||
let finished_loading = use_state(|| false);
|
let finished_loading = use_state(|| false);
|
||||||
@ -59,36 +76,53 @@ pub fn details_page(props: &DetailsPageProps) -> Html {
|
|||||||
Ok(listing) => {
|
Ok(listing) => {
|
||||||
listing_handle.set(listing);
|
listing_handle.set(listing);
|
||||||
finished_loading.set(true);
|
finished_loading.set(true);
|
||||||
},
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
error!("Error fetching listing. Error: {:?}", error);
|
error!("Error fetching listing. Error: {:?}", error);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
let listing = (*listing_handle).clone();
|
let listing = (*listing_handle).clone();
|
||||||
let mut forsale_units: Vec<&Unit> = listing.units.iter().filter(|unit| unit.unit_type == UnitType::ForSale).collect();
|
let mut forsale_units: Vec<&Unit> = listing
|
||||||
let mut not_forsale_units: Vec<&Unit> = listing.units.iter().filter(|unit| unit.unit_type == UnitType::NotForSale).collect();
|
.units
|
||||||
|
.iter()
|
||||||
|
.filter(|unit| unit.unit_type == UnitType::ForSale)
|
||||||
|
.collect();
|
||||||
|
let mut not_forsale_units: Vec<&Unit> = listing
|
||||||
|
.units
|
||||||
|
.iter()
|
||||||
|
.filter(|unit| unit.unit_type == UnitType::NotForSale)
|
||||||
|
.collect();
|
||||||
|
|
||||||
let mut organized_units = Vec::new();
|
let mut organized_units = Vec::new();
|
||||||
organized_units.append(&mut forsale_units);
|
organized_units.append(&mut forsale_units);
|
||||||
organized_units.append(&mut not_forsale_units);
|
organized_units.append(&mut not_forsale_units);
|
||||||
|
|
||||||
let project_title = format!("{} en {}, {}", &listing.project.project_type, &listing.location.district, &listing.location.city);
|
let project_title = format!(
|
||||||
|
"{} en {}, {}",
|
||||||
|
&listing.project.project_type, &listing.location.district, &listing.location.city
|
||||||
|
);
|
||||||
//let project_description = &listing.project.description;
|
//let project_description = &listing.project.description;
|
||||||
let project_media_list = listing.project.media.media_list;
|
let project_media_list = listing.project.media.media_list;
|
||||||
let project_type = listing.project.project_type.to_string();
|
let project_type = listing.project.project_type.to_string();
|
||||||
let project_condition = listing.project.project_condition.to_string();
|
let project_condition = listing.project.project_condition.to_string();
|
||||||
let project_est_finish_date = format!("{} {}", listing.project.finish_date.format("%m/%Y"), if listing.project.finish_date.timestamp_millis() <= chrono::Utc::now().timestamp_millis() {
|
let project_est_finish_date = format!(
|
||||||
|
"{} {}",
|
||||||
|
listing.project.finish_date.format("%m/%Y"),
|
||||||
|
if listing.project.finish_date.timestamp_millis() <= chrono::Utc::now().timestamp_millis() {
|
||||||
"(Listo)"
|
"(Listo)"
|
||||||
} else {""});
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
);
|
||||||
let project_floors = listing.project.floors.to_string();
|
let project_floors = listing.project.floors.to_string();
|
||||||
|
|
||||||
let cloned_selected_unit_handle = selected_unit_handle.clone();
|
let cloned_selected_unit_handle = selected_unit_handle.clone();
|
||||||
|
|
||||||
let selected_unit_opt = organized_units.get(*cloned_selected_unit_handle);
|
let selected_unit_opt = organized_units.get(*cloned_selected_unit_handle);
|
||||||
html!{
|
html! {
|
||||||
<>
|
<>
|
||||||
<NavigationBar/>
|
<NavigationBar/>
|
||||||
<FloatingWidget phone_number={
|
<FloatingWidget phone_number={
|
||||||
@ -276,7 +310,7 @@ pub fn details_page(props: &DetailsPageProps) -> Html {
|
|||||||
|
|
||||||
#[derive(Properties, PartialEq)]
|
#[derive(Properties, PartialEq)]
|
||||||
pub struct DetailsPageProps {
|
pub struct DetailsPageProps {
|
||||||
pub project_id: Uuid
|
pub project_id: Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format_phone_number<'a>(phone_number: &'a String) -> String {
|
pub fn format_phone_number<'a>(phone_number: &'a String) -> String {
|
||||||
@ -289,8 +323,10 @@ pub fn format_phone_number<'a>(phone_number: &'a String) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_unit_selection_menu(units: &Vec<&Unit>, selected_unit_handle: UseStateHandle<usize>) -> Html {
|
pub fn create_unit_selection_menu(
|
||||||
|
units: &Vec<&Unit>,
|
||||||
|
selected_unit_handle: UseStateHandle<usize>,
|
||||||
|
) -> Html {
|
||||||
html! {
|
html! {
|
||||||
<div class={"details-body-units-selection-container"}>
|
<div class={"details-body-units-selection-container"}>
|
||||||
{ // Maps all the units to generate the unit selection menu
|
{ // Maps all the units to generate the unit selection menu
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use yew_router::prelude::{use_navigator};
|
use yew_router::prelude::use_navigator;
|
||||||
|
|
||||||
use crate::{components::{nav_bar::NavigationBar, footer::PageFooter}, routes::main_router::Route};
|
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
components::{footer::PageFooter, nav_bar::NavigationBar},
|
||||||
|
routes::main_router::Route,
|
||||||
|
};
|
||||||
|
|
||||||
#[function_component(LandingPage)]
|
#[function_component(LandingPage)]
|
||||||
pub fn landing_page() -> Html {
|
pub fn landing_page() -> Html {
|
||||||
|
|
||||||
let navigator = use_navigator().unwrap();
|
let navigator = use_navigator().unwrap();
|
||||||
let go_to_proyect_page_on_click = Callback::from(move |_| navigator.push(&Route::Search));
|
let go_to_proyect_page_on_click = Callback::from(move |_| navigator.push(&Route::Search));
|
||||||
|
|
||||||
html!{
|
html! {
|
||||||
<>
|
<>
|
||||||
<NavigationBar/>
|
<NavigationBar/>
|
||||||
<div class={"page-container"} style={"margin-bottom: 10vh"}> //TODO: remove this margin when landing page done
|
<div class={"page-container"} style={"margin-bottom: 10vh"}> //TODO: remove this margin when landing page done
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
pub mod landing;
|
|
||||||
pub mod search;
|
|
||||||
pub mod details;
|
|
||||||
pub mod not_found;
|
|
||||||
pub mod contact;
|
|
||||||
pub mod admin;
|
pub mod admin;
|
||||||
pub mod agents;
|
pub mod agents;
|
||||||
|
pub mod contact;
|
||||||
|
pub mod details;
|
||||||
|
pub mod landing;
|
||||||
|
pub mod not_found;
|
||||||
|
pub mod search;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
use crate::components::{nav_bar::NavigationBar, footer::PageFooter};
|
use crate::components::{footer::PageFooter, nav_bar::NavigationBar};
|
||||||
|
|
||||||
#[function_component(NotFoundPage)]
|
#[function_component(NotFoundPage)]
|
||||||
pub fn not_found_page() -> Html {
|
pub fn not_found_page() -> Html {
|
||||||
|
@ -1,11 +1,23 @@
|
|||||||
use jl_types::{domain::{project_state::ProjectState, project_type::ProjectType, project_condition::ProjectCondition}, dto::{filters::Filter, project_card::ProjectCardDto}};
|
use jl_types::{
|
||||||
|
domain::{
|
||||||
|
project_condition::ProjectCondition, project_state::ProjectState, project_type::ProjectType,
|
||||||
|
},
|
||||||
|
dto::{filters::Filter, project_card::ProjectCardDto},
|
||||||
|
};
|
||||||
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::{DropDown, DropDownProps},
|
||||||
|
vdom::comp_with,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
api::backend::{
|
||||||
|
get_all_cities, get_all_districts_in_city, get_all_projects_with_filters_paged,
|
||||||
|
},
|
||||||
|
components::{footer::PageFooter, nav_bar::NavigationBar, project_card::ProjectCard},
|
||||||
|
};
|
||||||
use jl_types::domain::option_wrapper::OptionWrapper;
|
use jl_types::domain::option_wrapper::OptionWrapper;
|
||||||
use crate::{components::{nav_bar::NavigationBar, project_card::ProjectCard, footer::PageFooter}, 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() -> Html {
|
pub fn search_page() -> Html {
|
||||||
@ -26,51 +38,70 @@ 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 mut cities: Vec<OptionWrapper<String>> = cities.into_iter().map(|location| OptionWrapper::new(Some(location))).collect();
|
let mut cities: Vec<OptionWrapper<String>> = cities
|
||||||
|
.into_iter()
|
||||||
|
.map(|location| OptionWrapper::new(Some(location)))
|
||||||
|
.collect();
|
||||||
cities.insert(0, OptionWrapper::new(None));
|
cities.insert(0, OptionWrapper::new(None));
|
||||||
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}"),
|
||||||
};
|
};
|
||||||
match get_all_projects_with_filters_paged(&(*page_counter), vec![Filter::InConstruction]).await {
|
match get_all_projects_with_filters_paged(
|
||||||
|
&(*page_counter),
|
||||||
|
vec![Filter::InConstruction],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(projects) => {
|
Ok(projects) => {
|
||||||
search_results_handle.set(projects);
|
search_results_handle.set(projects);
|
||||||
finished_loading.set(true);
|
finished_loading.set(true);
|
||||||
},
|
}
|
||||||
Err(error) => log::error!("Error in loading projects: {error}"),
|
Err(error) => log::error!("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(Some(props.project_state.clone())));
|
// 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>> =
|
||||||
let unit_rooms_filter: UseStateHandle<OptionWrapper<usize>> = use_state(|| OptionWrapper::new(None));
|
use_state(|| OptionWrapper::new(None));
|
||||||
|
let unit_rooms_filter: UseStateHandle<OptionWrapper<usize>> =
|
||||||
|
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 {
|
||||||
initial: OptionWrapper::new(None),
|
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)) ],
|
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: {
|
selection_changed: {
|
||||||
let cloned_project_type_filter = project_type_filter.clone();
|
let cloned_project_type_filter = project_type_filter.clone();
|
||||||
Callback::from(move |project_type: OptionWrapper<ProjectType>| {
|
Callback::from(move |project_type: OptionWrapper<ProjectType>| {
|
||||||
info!("{}", project_type.to_string());
|
info!("{}", project_type.to_string());
|
||||||
cloned_project_type_filter.set(project_type)
|
cloned_project_type_filter.set(project_type)
|
||||||
}
|
})
|
||||||
)},
|
},
|
||||||
class_css: Some("project-search-filter-item".into())
|
class_css: Some("project-search-filter-item".into()),
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -88,17 +119,22 @@ pub fn search_page() -> 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),
|
||||||
options: vec![OptionWrapper::new(None), OptionWrapper::new(Some(ProjectCondition::New)), OptionWrapper::new(Some(ProjectCondition::Resale)) ],
|
options: vec![
|
||||||
|
OptionWrapper::new(None),
|
||||||
|
OptionWrapper::new(Some(ProjectCondition::New)),
|
||||||
|
OptionWrapper::new(Some(ProjectCondition::Resale)),
|
||||||
|
],
|
||||||
selection_changed: {
|
selection_changed: {
|
||||||
let cloned_project_condition_filter = project_condition_filter.clone();
|
let cloned_project_condition_filter = project_condition_filter.clone();
|
||||||
Callback::from(move |project_condition: OptionWrapper<ProjectCondition>| {
|
Callback::from(move |project_condition: OptionWrapper<ProjectCondition>| {
|
||||||
info!("{}", project_condition.to_string());
|
info!("{}", project_condition.to_string());
|
||||||
cloned_project_condition_filter.set(project_condition)
|
cloned_project_condition_filter.set(project_condition)
|
||||||
}
|
})
|
||||||
)},
|
},
|
||||||
class_css: Some("project-search-filter-item".into())
|
class_css: Some("project-search-filter-item".into()),
|
||||||
});
|
});
|
||||||
|
|
||||||
let project_city_drop_down = comp_with::<DropDown<OptionWrapper<String>>>(DropDownProps {
|
let project_city_drop_down = comp_with::<DropDown<OptionWrapper<String>>>(DropDownProps {
|
||||||
@ -114,16 +150,19 @@ pub fn search_page() -> Html {
|
|||||||
wasm_bindgen_futures::spawn_local(async move {
|
wasm_bindgen_futures::spawn_local(async move {
|
||||||
match get_all_districts_in_city(&project_city.to_string()).await {
|
match get_all_districts_in_city(&project_city.to_string()).await {
|
||||||
Ok(districts) => {
|
Ok(districts) => {
|
||||||
let mut districts_vec: Vec<OptionWrapper<String>> = districts.into_iter().map(|district| OptionWrapper::new(Some(district))).collect();
|
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_vec.insert(0, OptionWrapper::new(None));
|
||||||
districts_handle.set(districts_vec);
|
districts_handle.set(districts_vec);
|
||||||
},
|
}
|
||||||
Err(error) => log::error!("Error in dropdown callback: {}", error),
|
Err(error) => log::error!("Error in dropdown callback: {}", error),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
})
|
||||||
)},
|
},
|
||||||
class_css: Some("project-search-filter-item".into())
|
class_css: Some("project-search-filter-item".into()),
|
||||||
});
|
});
|
||||||
|
|
||||||
let project_district_drop_down = comp_with::<DropDown<OptionWrapper<String>>>(DropDownProps {
|
let project_district_drop_down = comp_with::<DropDown<OptionWrapper<String>>>(DropDownProps {
|
||||||
@ -133,21 +172,33 @@ pub fn search_page() -> Html {
|
|||||||
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>| {
|
||||||
cloned_project_district_filter.set(project_district)
|
cloned_project_district_filter.set(project_district)
|
||||||
}
|
})
|
||||||
)},
|
},
|
||||||
class_css: Some("project-search-filter-item".into())
|
class_css: Some("project-search-filter-item".into()),
|
||||||
});
|
});
|
||||||
|
|
||||||
let unit_room_amount_drop_down = comp_with::<DropDown<OptionWrapper<usize>>>(DropDownProps {
|
let unit_room_amount_drop_down = comp_with::<DropDown<OptionWrapper<usize>>>(DropDownProps {
|
||||||
initial: OptionWrapper::new(None),
|
initial: OptionWrapper::new(None),
|
||||||
options: vec![OptionWrapper::new(None), OptionWrapper::new(Some(1)), OptionWrapper::new(Some(2)), OptionWrapper::new(Some(3)), OptionWrapper::new(Some(4)), OptionWrapper::new(Some(5)), OptionWrapper::new(Some(6)), OptionWrapper::new(Some(7)), OptionWrapper::new(Some(8)), OptionWrapper::new(Some(9)), OptionWrapper::new(Some(10))],
|
options: vec![
|
||||||
|
OptionWrapper::new(None),
|
||||||
|
OptionWrapper::new(Some(1)),
|
||||||
|
OptionWrapper::new(Some(2)),
|
||||||
|
OptionWrapper::new(Some(3)),
|
||||||
|
OptionWrapper::new(Some(4)),
|
||||||
|
OptionWrapper::new(Some(5)),
|
||||||
|
OptionWrapper::new(Some(6)),
|
||||||
|
OptionWrapper::new(Some(7)),
|
||||||
|
OptionWrapper::new(Some(8)),
|
||||||
|
OptionWrapper::new(Some(9)),
|
||||||
|
OptionWrapper::new(Some(10)),
|
||||||
|
],
|
||||||
selection_changed: {
|
selection_changed: {
|
||||||
let unit_rooms_filter = unit_rooms_filter.clone();
|
let unit_rooms_filter = unit_rooms_filter.clone();
|
||||||
Callback::from(move |unit_room_amount: OptionWrapper<usize>| {
|
Callback::from(move |unit_room_amount: OptionWrapper<usize>| {
|
||||||
unit_rooms_filter.set(unit_room_amount)
|
unit_rooms_filter.set(unit_room_amount)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
class_css: Some("project-search-filter-item".into())
|
class_css: Some("project-search-filter-item".into()),
|
||||||
});
|
});
|
||||||
|
|
||||||
let search_onclick = {
|
let search_onclick = {
|
||||||
@ -168,51 +219,50 @@ pub fn search_page() -> Html {
|
|||||||
}
|
}
|
||||||
match &(*project_type_filter).option {
|
match &(*project_type_filter).option {
|
||||||
Some(project_type) => filters.push(Filter::ByProjectType(project_type.clone())),
|
Some(project_type) => filters.push(Filter::ByProjectType(project_type.clone())),
|
||||||
None => {},
|
None => {}
|
||||||
};
|
};
|
||||||
match &(*project_condition_filter).option {
|
match &(*project_condition_filter).option {
|
||||||
Some(project_condition) => filters.push(Filter::ByProjectCondition(project_condition.clone())),
|
Some(project_condition) => {
|
||||||
None => {},
|
filters.push(Filter::ByProjectCondition(project_condition.clone()))
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
};
|
};
|
||||||
match &(*project_city_filter).option {
|
match &(*project_city_filter).option {
|
||||||
Some(project_city) => filters.push(Filter::InCity(project_city.clone())),
|
Some(project_city) => filters.push(Filter::InCity(project_city.clone())),
|
||||||
None => {},
|
None => {}
|
||||||
};
|
};
|
||||||
match &(*project_district_filter).option {
|
match &(*project_district_filter).option {
|
||||||
Some(project_district) => filters.push(Filter::InDistrict(project_district.clone())),
|
Some(project_district) => {
|
||||||
None => {},
|
filters.push(Filter::InDistrict(project_district.clone()))
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
};
|
};
|
||||||
match &(*unit_rooms_filter).option {
|
match &(*unit_rooms_filter).option {
|
||||||
Some(rooms_filter) => filters.push(Filter::ByRoomCount(*rooms_filter as i32)),
|
Some(rooms_filter) => filters.push(Filter::ByRoomCount(*rooms_filter as i32)),
|
||||||
None => {},
|
None => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
let search_results_handle = search_results_handle.clone();
|
let search_results_handle = search_results_handle.clone();
|
||||||
let page_counter = page_counter.clone();
|
let page_counter = page_counter.clone();
|
||||||
wasm_bindgen_futures::spawn_local(async move {
|
wasm_bindgen_futures::spawn_local(async move {
|
||||||
match get_all_projects_with_filters_paged(&(*page_counter), filters).await {
|
match get_all_projects_with_filters_paged(&(*page_counter), filters).await {
|
||||||
Ok(projects) => {
|
Ok(projects) => search_results_handle.set(projects),
|
||||||
search_results_handle.set(projects)
|
|
||||||
},
|
|
||||||
Err(error) => log::error!("Error in loading projects: {error}"),
|
Err(error) => log::error!("Error in loading projects: {error}"),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
})};
|
})
|
||||||
|
};
|
||||||
|
|
||||||
let inconstruction_state_select_onclick = {
|
let inconstruction_state_select_onclick = {
|
||||||
let project_state_filter = project_state_filter_handle.clone();
|
let project_state_filter = project_state_filter_handle.clone();
|
||||||
Callback::from(move |_| {
|
Callback::from(move |_| project_state_filter.set(ProjectState::InConstruction))
|
||||||
project_state_filter.set(ProjectState::InConstruction)
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let finished_state_select_onclick = {
|
let finished_state_select_onclick = {
|
||||||
let project_state_filter = project_state_filter_handle.clone();
|
let project_state_filter = project_state_filter_handle.clone();
|
||||||
Callback::from(move |_| {
|
Callback::from(move |_| project_state_filter.set(ProjectState::Finished))
|
||||||
project_state_filter.set(ProjectState::Finished)
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
html!{
|
html! {
|
||||||
<>
|
<>
|
||||||
<NavigationBar/>
|
<NavigationBar/>
|
||||||
<div class={"page-container"}>
|
<div class={"page-container"}>
|
||||||
|
@ -1,9 +1,24 @@
|
|||||||
use yew_router::prelude::*;
|
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
use yew_router::prelude::*;
|
||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{pages::{landing::LandingPage, search::{SearchPage}, details::DetailsPage, not_found::NotFoundPage, contact::ContactPage, admin::{login::AdminLoginPage, start::AdminStart, projects::AdminProjects, agents::AdminAgents, contacts::AdminContacts, edit::{EditType, EditItem, AdminEditPage}}, agents::AgentsPage}};
|
use crate::pages::{
|
||||||
|
admin::{
|
||||||
|
agents::AdminAgents,
|
||||||
|
contacts::AdminContacts,
|
||||||
|
edit::{AdminEditPage, EditItem, EditType},
|
||||||
|
login::AdminLoginPage,
|
||||||
|
projects::AdminProjects,
|
||||||
|
start::AdminStart,
|
||||||
|
},
|
||||||
|
agents::AgentsPage,
|
||||||
|
contact::ContactPage,
|
||||||
|
details::DetailsPage,
|
||||||
|
landing::LandingPage,
|
||||||
|
not_found::NotFoundPage,
|
||||||
|
search::SearchPage,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone, Routable, PartialEq)]
|
#[derive(Clone, Routable, PartialEq)]
|
||||||
pub enum Route {
|
pub enum Route {
|
||||||
@ -29,7 +44,10 @@ pub enum Route {
|
|||||||
#[at("/admin/contacts")]
|
#[at("/admin/contacts")]
|
||||||
AdminContacts,
|
AdminContacts,
|
||||||
#[at("/admin/edit/:edit_type/:edit_item")]
|
#[at("/admin/edit/:edit_type/:edit_item")]
|
||||||
AdminEdit { edit_type: EditType, edit_item: EditItem },
|
AdminEdit {
|
||||||
|
edit_type: EditType,
|
||||||
|
edit_item: EditItem,
|
||||||
|
},
|
||||||
|
|
||||||
#[not_found]
|
#[not_found]
|
||||||
#[at("/404")]
|
#[at("/404")]
|
||||||
@ -50,6 +68,9 @@ pub fn switch(routes: Route) -> Html {
|
|||||||
Route::AdminProjects => html! { <AdminProjects/> },
|
Route::AdminProjects => html! { <AdminProjects/> },
|
||||||
Route::AdminAgents => html! { <AdminAgents/> },
|
Route::AdminAgents => html! { <AdminAgents/> },
|
||||||
Route::AdminContacts => html! { <AdminContacts/> },
|
Route::AdminContacts => html! { <AdminContacts/> },
|
||||||
Route::AdminEdit { edit_type, edit_item } => html! { <AdminEditPage edit_item={edit_item} edit_type={edit_type}/> },
|
Route::AdminEdit {
|
||||||
|
edit_type,
|
||||||
|
edit_item,
|
||||||
|
} => html! { <AdminEditPage edit_item={edit_item} edit_type={edit_type}/> },
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,12 +1,13 @@
|
|||||||
use super::storage;
|
use super::storage;
|
||||||
|
|
||||||
|
|
||||||
pub fn get_admin_token_from_storage() -> Option<String> {
|
pub fn get_admin_token_from_storage() -> Option<String> {
|
||||||
match storage::get_from_local_storage(storage::StorageKey::AdminUser) {
|
match storage::get_from_local_storage(storage::StorageKey::AdminUser) {
|
||||||
Ok(opt) => opt,
|
Ok(opt) => opt,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
log::error!("No user stored when attempting to use admin panel. Redirect to admin panel.");
|
log::error!(
|
||||||
|
"No user stored when attempting to use admin panel. Redirect to admin panel."
|
||||||
|
);
|
||||||
None
|
None
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1 @@
|
|||||||
|
|
@ -1,5 +1,5 @@
|
|||||||
use wasm_bindgen::{JsCast, UnwrapThrowExt};
|
use wasm_bindgen::{JsCast, UnwrapThrowExt};
|
||||||
use web_sys::{Event, HtmlInputElement, InputEvent, HtmlTextAreaElement};
|
use web_sys::{Event, HtmlInputElement, HtmlTextAreaElement, InputEvent};
|
||||||
|
|
||||||
pub fn get_value_from_input_event(e: InputEvent) -> String {
|
pub fn get_value_from_input_event(e: InputEvent) -> String {
|
||||||
let event: Event = e.dyn_into().unwrap_throw();
|
let event: Event = e.dyn_into().unwrap_throw();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
|
pub mod admin_panel;
|
||||||
pub mod get_value;
|
pub mod get_value;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
pub mod storage;
|
pub mod storage;
|
||||||
pub mod admin_panel;
|
|
@ -1,6 +1,6 @@
|
|||||||
use std::fmt::{Display};
|
use std::fmt::Display;
|
||||||
|
|
||||||
use err::{MessageResource, Error};
|
use err::{Error, MessageResource};
|
||||||
use web_sys::window;
|
use web_sys::window;
|
||||||
|
|
||||||
pub enum StorageKey {
|
pub enum StorageKey {
|
||||||
|
Loading…
Reference in New Issue
Block a user