Cargo fmt holy

This commit is contained in:
Franklin 2023-04-23 11:11:38 -04:00
parent 6b20ac66f1
commit fc2a2510ec
43 changed files with 940 additions and 376 deletions

View File

@ -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"] }

View 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;
}

View File

@ -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">

View File

@ -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
}*/

View File

@ -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 =
@ -25,7 +24,7 @@ pub async fn perform_request_without_client<B: Serialize, R: DeserializeOwned>(
if let Some(parameters) = params { if let Some(parameters) = params {
req_incomplete = req_incomplete.query(&parameters) req_incomplete = req_incomplete.query(&parameters)
} }
let req_complete = match body { let req_complete = match body {
Some(b) => req_incomplete.json(&b), Some(b) => req_incomplete.json(&b),
None => req_incomplete.header("content-length", 0), None => req_incomplete.header("content-length", 0),
@ -60,4 +59,4 @@ pub async fn perform_request_without_client<B: Serialize, R: DeserializeOwned>(
Err(Error::Network(MessageResource::from(e))) Err(Error::Network(MessageResource::from(e)))
} }
} }
} }

View File

@ -1,2 +1,2 @@
pub mod backend;
pub mod base; pub mod base;
pub mod backend;

View File

@ -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();
@ -13,7 +12,7 @@ pub fn admin_navigation_bar() -> Html {
let cloned_navigator_2 = navigator.clone(); let cloned_navigator_2 = navigator.clone();
let cloned_navigator_3 = navigator.clone(); let cloned_navigator_3 = navigator.clone();
let cloned_navigator_4 = navigator.clone(); let cloned_navigator_4 = navigator.clone();
html! { html! {
<div class={"admin-navbar-background"}> <div class={"admin-navbar-background"}>
<div class={"admin-navbar-image"}> <div class={"admin-navbar-image"}>
@ -27,8 +26,8 @@ pub fn admin_navigation_bar() -> Html {
<div class={ if current_route.is_some() && matches!(current_route.clone().unwrap(), Route::AdminContacts) {"admin-navbar-element-selected"} else {"admin-navbar-element"} } onclick={move |_| cloned_navigator_4.push(&Route::AdminContacts)}>{"Mensajes"}</div> <div class={ if current_route.is_some() && matches!(current_route.clone().unwrap(), Route::AdminContacts) {"admin-navbar-element-selected"} else {"admin-navbar-element"} } onclick={move |_| cloned_navigator_4.push(&Route::AdminContacts)}>{"Mensajes"}</div>
<div class={"admin-navbar-divider"}></div> <div class={"admin-navbar-divider"}></div>
</div> </div>
</div> </div>
} }
} }

View File

@ -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! {
@ -59,4 +65,4 @@ pub fn admin_project(props: &AdminProjectProps) -> Html {
pub struct AdminProjectProps { pub struct AdminProjectProps {
pub project: ProjectCardDto, pub project: ProjectCardDto,
pub index: usize, pub index: usize,
} }

View File

@ -30,4 +30,4 @@ pub fn agent_card(props: &AgentCardProps) -> Html {
#[derive(Properties, PartialEq)] #[derive(Properties, PartialEq)]
pub struct AgentCardProps { pub struct AgentCardProps {
pub agent: Agent, pub agent: Agent,
} }

View File

@ -1,25 +1,25 @@
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;
/// The input type date field in html will always guarantee a valid date string. /// The input type date field in html will always guarantee a valid date string.
#[function_component(DatePicker)] #[function_component(DatePicker)]
pub fn datepicker(props: &DatePickerProps) -> Html { pub fn datepicker(props: &DatePickerProps) -> Html {
let date_handle = props.value.clone(); let date_handle = props.value.clone();
let optional_cb = props.onchange.clone(); let optional_cb = props.onchange.clone();
let cb = Callback::from(move |e: InputEvent| { let cb = Callback::from(move |e: InputEvent| {
match parse_date(get_value_from_input_event(e)) { match parse_date(get_value_from_input_event(e)) {
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(())
}, }
} }
} }

View File

@ -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
options .clone()
.into_iter()
.map(|option| OptionWrapper::new(Some(option)))
.collect();
if props.has_none {
options.insert(0, OptionWrapper::new(None));
}
options
},
selection_changed: selection_changed_cb.clone(),
class_css: Some("admin-dropdown".into()),
}, },
selection_changed: selection_changed_cb.clone(), );
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,
} }

View File

@ -3,11 +3,11 @@ 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>
</div> </div>
<div class={"details-body-feature-item-text-container"}> <div class={"details-body-feature-item-text-container"}>
<div class={"details-body-feature-item-text-title"}> <div class={"details-body-feature-item-text-title"}>
{props.title} {props.title}
@ -27,4 +27,4 @@ pub struct FeatureItemProps {
pub title: String, pub title: String,
pub subtitle: String, pub subtitle: String,
pub icon: String, pub icon: String,
} }

View File

@ -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);
@ -20,4 +19,4 @@ pub fn floating_widget(props: &FloatingWidgetProps) -> Html {
pub struct FloatingWidgetProps { pub struct FloatingWidgetProps {
pub project_id: Uuid, pub project_id: Uuid,
pub phone_number: String, pub phone_number: String,
} }

View File

@ -27,9 +27,9 @@ pub fn page_footer() -> Html {
</div> </div>
</div> </div>
/* /*
<div class={"footer-call-to-action-container"}> <div class={"footer-call-to-action-container"}>
@ -39,7 +39,7 @@ pub fn page_footer() -> Html {
</div> </div>
</div> </div>
*/ */
</div> </div>
} }
} }

View 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>>,
}

View File

@ -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);
} }
@ -47,7 +46,7 @@ pub fn media_slideshow(props: &MediaSlideshowProps) -> Html {
"display: none;" "display: none;"
}} }}
src={format!("{url}?autoplay=1&mute=1")}> src={format!("{url}?autoplay=1&mute=1")}>
</iframe> </iframe>
} }
} }
}).collect::<Html>() }).collect::<Html>()
@ -69,4 +68,4 @@ pub fn media_slideshow(props: &MediaSlideshowProps) -> Html {
#[derive(Properties, PartialEq, PartialOrd)] #[derive(Properties, PartialEq, PartialOrd)]
pub struct MediaSlideshowProps { pub struct MediaSlideshowProps {
pub media_list: Vec<Media>, pub media_list: Vec<Media>,
} }

View File

@ -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 datepicker;
pub mod dropdown; pub mod dropdown;
pub mod datepicker; 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;

View File

@ -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(|| {
WindowDevice::Desktop if stdweb::web::window().inner_width() > 750 {
} else { WindowDevice::Desktop
WindowDevice::Mobile } else {
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| {
@ -37,16 +42,15 @@ pub fn navigation_bar() -> Html {
window_device_handle_cloned.set(WindowDevice::Mobile) window_device_handle_cloned.set(WindowDevice::Mobile)
} }
}); });
html! { html! {
<div class={"navbar-background"}> <div class={"navbar-background"}>
<div class={"navbar-container"}> <div class={"navbar-container"}>
<div class={"navbar-brand-container"} onclick={move |_| cloned_navigator_5.push(&Route::LandingPage)}> <div class={"navbar-brand-container"} onclick={move |_| cloned_navigator_5.push(&Route::LandingPage)}>
<img class={"navbar-image"} src={ if (*window_device_handle) == WindowDevice::Mobile {"images/logo-white.png"} else {"images/logo-color.png"}} alt=""/> <img class={"navbar-image"} src={ if (*window_device_handle) == WindowDevice::Mobile {"images/logo-white.png"} else {"images/logo-color.png"}} alt=""/>
</div> </div>
<div class={if *navbar_toggle {"navbar-closed"} else {"navbar-open"}}> <div class={if *navbar_toggle {"navbar-closed"} else {"navbar-open"}}>
<div onclick={move |_| cloned_navigator_1.push(&Route::LandingPage)} class={ <div onclick={move |_| cloned_navigator_1.push(&Route::LandingPage)} class={
if current_route.is_some() && matches!(current_route.clone().unwrap(), Route::LandingPage) {"navbar-item-selected"} else {"navbar-item"} if current_route.is_some() && matches!(current_route.clone().unwrap(), Route::LandingPage) {"navbar-item-selected"} else {"navbar-item"}
@ -109,7 +113,7 @@ pub fn navigation_bar() -> Html {
} }
} }
} }
</div> </div>
} }
} }
@ -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,
} }

View 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,
}

View File

@ -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,30 +24,35 @@ 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 {}, {}",
let project_price = format!("Desde US${}", match props.project.starts_from { props.project.project_type, props.project.district, props.project.city
Some(price) => { );
let price_separated = price.separate_with_commas();
if price_separated.contains(".") { let project_price = format!(
price_separated "Desde US${}",
} else { match props.project.starts_from {
format!("{price_separated}.00") Some(price) => {
let price_separated = price.separate_with_commas();
if price_separated.contains(".") {
price_separated
} else {
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"}/>
<div class={"project-search-result-card-title"}> <div class={"project-search-result-card-title"}>
{project_title} {project_title}
</div> </div>
@ -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,
} }

View File

@ -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) => {
Some(onchange) => { onchange.emit(value.clone()); }, onchange.emit(value.clone());
None => {} }
}; None => {}
handle.set(value); };
}, handle.set(value);
TextFieldType::TextArea => { }
let value = get_value_from_input_event(e); TextFieldType::TextArea => {
match onchange.clone() { let value = get_value_from_textarea_event(e);
Some(onchange) => { onchange.emit(value.clone()); }, match onchange.clone() {
None => {} Some(onchange) => {
}; onchange.emit(value.clone());
handle.set(value); }
}, None => {}
};
handle.set(value);
} }
}) })
}; };
@ -42,7 +43,7 @@ pub fn textfield(props: &TextFieldProps) -> Html {
html! { <textarea class={"textarea"} oninput={on_input_changed} value={(*props.value).clone()}/> } html! { <textarea class={"textarea"} oninput={on_input_changed} value={(*props.value).clone()}/> }
} }
} }
</div> </div>
} }
} }
@ -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)]
@ -77,4 +78,4 @@ pub fn get_value_from_textarea_event(e: InputEvent) -> String {
let event_target = event.target().unwrap_throw(); let event_target = event.target().unwrap_throw();
let target: HtmlTextAreaElement = event_target.dyn_into().unwrap_throw(); let target: HtmlTextAreaElement = event_target.dyn_into().unwrap_throw();
target.value() target.value()
} }

View File

@ -4,4 +4,4 @@ pub const NAVBAR_COL_CONTACTO: &str = "Vende con nosotros";
pub const NAVBAR_COL_AGENTES: &str = "Agentes"; pub const NAVBAR_COL_AGENTES: &str = "Agentes";
pub const PRIMARY_COLOR: &str = "#41BDD9"; pub const PRIMARY_COLOR: &str = "#41BDD9";
pub const SECONDARY_COLOR: &str = "#5D6A73"; pub const SECONDARY_COLOR: &str = "#5D6A73";

View File

@ -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),
}; };
}); });
@ -29,4 +28,4 @@ pub fn admin_agents() -> Html {
</div> </div>
</> </>
} }
} }

View File

@ -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)
}
}; };
}); });
}); });
@ -23,9 +25,9 @@ pub fn admin_contacts() -> Html {
<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"}>{"Solicitudes de Contacto"}</div> <div class={"admin-panel-page-title"}>{"Solicitudes de Contacto"}</div>
</div> </div>
</div> </div>
</> </>
} }
} }

View File

@ -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",
@ -59,21 +78,21 @@ pub fn edit_page(props: &AdminEditPageProps) -> Html {
</div> </div>
</div> </div>
</> </>
} }
} }
#[derive(PartialEq, Clone)] #[derive(PartialEq, Clone)]
pub enum EditItem { pub enum EditItem {
Agent, Agent,
Project, Project,
/// Project Uid /// Project Uid
Unit(Uuid), Unit(Uuid),
} }
#[derive(PartialEq, Clone)] #[derive(PartialEq, Clone)]
pub enum EditType { pub enum EditType {
New, Existing(Uuid) New,
Existing(Uuid),
} }
#[derive(Properties, PartialEq)] #[derive(Properties, PartialEq)]
@ -92,7 +111,7 @@ pub struct ProjectFieldsProps {
pub fn generate_fields_for_project(props: &ProjectFieldsProps) -> Html { pub fn generate_fields_for_project(props: &ProjectFieldsProps) -> Html {
let user_typed = use_state(|| false); let user_typed = use_state(|| false);
let listing_opt = props.listing.clone(); let listing_opt = props.listing.clone();
let location_city = use_state_eq(|| String::new()); let location_city = use_state_eq(|| String::new());
let location_district = use_state_eq(|| String::new()); let location_district = use_state_eq(|| String::new());
let agent: UseStateHandle<Option<Agent>> = use_state_eq(|| None); let agent: UseStateHandle<Option<Agent>> = use_state_eq(|| None);
@ -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,12 +171,14 @@ 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}")
}
}; };
}) })
}); });
if !*user_typed { if !*user_typed {
location_city.set(listing_opt.clone().unwrap_or_default().location.city); location_city.set(listing_opt.clone().unwrap_or_default().location.city);
location_district.set(listing_opt.clone().unwrap_or_default().location.district); location_district.set(listing_opt.clone().unwrap_or_default().location.district);
agent.set(match listing_opt.clone() { agent.set(match listing_opt.clone() {
@ -169,12 +198,12 @@ pub fn generate_fields_for_project(props: &ProjectFieldsProps) -> Html {
None => None, None => None,
}); });
project_description.set(match listing_opt.clone() { project_description.set(match listing_opt.clone() {
Some(listing) => listing.project.description, Some(listing) => listing.project.description,
None => "".into(), None => "".into(),
}); });
project_floors.set(match listing_opt.clone() { project_floors.set(match listing_opt.clone() {
Some(listing) => listing.project.floors.to_string(), Some(listing) => listing.project.floors.to_string(),
None => "".into(), None => "".into(),
}); });
project_admin_tag.set(match listing_opt.clone() { project_admin_tag.set(match listing_opt.clone() {
Some(listing) => match listing.project.admin_tag { Some(listing) => match listing.project.admin_tag {
@ -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>
@ -211,9 +261,16 @@ pub fn generate_fields_for_project(props: &ProjectFieldsProps) -> Html {
} }
} }
} }
{if (*project_condition).clone().is_none() { html! { } } else { {if (*project_condition).clone().is_none() {
html! { 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! {
<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>
<DropDown<ProjectCondition> options={vec![ProjectCondition::Resale, ProjectCondition::New]} selected={project_condition} onchange={onselect_cb3} /> <DropDown<ProjectCondition> options={vec![ProjectCondition::Resale, ProjectCondition::New]} selected={project_condition} onchange={onselect_cb3} />
@ -221,8 +278,15 @@ pub fn generate_fields_for_project(props: &ProjectFieldsProps) -> Html {
} }
} }
} }
{if (*project_type).clone().is_none() { html! { } } else { {if (*project_type).clone().is_none() {
html! { 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! {
<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>
<DropDown<ProjectType> options={vec![ProjectType::Apartamento, ProjectType::Casa, ProjectType::Oficina, ProjectType::Local, ProjectType::Solar]} selected={project_type} onchange={onselect_cb4} /> <DropDown<ProjectType> options={vec![ProjectType::Apartamento, ProjectType::Casa, ProjectType::Oficina, ProjectType::Local, ProjectType::Solar]} selected={project_type} onchange={onselect_cb4} />
@ -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(()),
} }
} }
} }

View File

@ -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 {
@ -25,7 +25,7 @@ pub fn login_page() -> Html {
}; };
let onclick = { let onclick = {
let error = error.clone(); let error = error.clone();
Callback::from(move |_| { Callback::from(move |_| {
let error = error.clone(); let error = error.clone();
let navigator = navigator.clone(); let navigator = navigator.clone();
@ -59,6 +59,6 @@ pub fn login_page() -> Html {
</div> </div>
</div> </div>
</div> </div>
} }
} }

View File

@ -1,6 +1,6 @@
pub mod login;
pub mod start;
pub mod projects;
pub mod contacts;
pub mod agents; pub mod agents;
pub mod edit; pub mod contacts;
pub mod edit;
pub mod login;
pub mod projects;
pub mod start;

View File

@ -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,25 +18,26 @@ 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>
<div class={"admin-projects-table"}> <div class={"admin-projects-table"}>
<div class={"admin-navbar-divider"}></div> <div class={"admin-navbar-divider"}></div>
{(*projects).clone().into_iter().enumerate().map(|(key, project)| html! { {(*projects).clone().into_iter().enumerate().map(|(key, project)| html! {
<> <>
<AdminProject project={project} index={key}/> <AdminProject project={project} index={key}/>
<div class={"admin-navbar-divider"}></div> <div class={"admin-navbar-divider"}></div>
</> </>
}).collect::<Html>()} }).collect::<Html>()}
@ -40,4 +46,4 @@ pub fn admin_projects() -> Html {
</div> </div>
</> </>
} }
} }

View File

@ -27,4 +27,4 @@ pub fn admin_start() -> Html {
</div> </div>
</> </>
} }
} }

View File

@ -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,16 +16,18 @@ 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),
};
};
}); });
}); });
html! { html! {
@ -52,4 +56,4 @@ pub fn agents_page() -> Html {
<PageFooter/> <PageFooter/>
</> </>
} }
} }

View File

@ -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}"
),
}; };
}); });
} }
@ -86,7 +95,7 @@ pub fn contact_page() -> Html {
<div class={"contact-us-textfield-label"}>{"Nombre"}</div> <div class={"contact-us-textfield-label"}>{"Nombre"}</div>
<input class={"contact-us-textfield"} oninput={on_fn_input_changed}/> <input class={"contact-us-textfield"} oninput={on_fn_input_changed}/>
</div> </div>
<div class={"contact-us-textfield-container"}> // Last name <div class={"contact-us-textfield-container"}> // Last name
<div class={"contact-us-textfield-label"}>{"Apellido"}</div> <div class={"contact-us-textfield-label"}>{"Apellido"}</div>
<input class={"contact-us-textfield"} oninput={on_ln_input_changed}/> <input class={"contact-us-textfield"} oninput={on_ln_input_changed}/>
@ -111,4 +120,4 @@ pub fn contact_page() -> Html {
<PageFooter/> <PageFooter/>
</> </>
} }
} }

View File

@ -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,39 +76,56 @@ 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!(
"(Listo)" "{} {}",
} else {""}); listing.project.finish_date.format("%m/%Y"),
if listing.project.finish_date.timestamp_millis() <= chrono::Utc::now().timestamp_millis() {
"(Listo)"
} 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={
if let Some(overriden_agent) = (*override_agent_handle).clone() if let Some(overriden_agent) = (*override_agent_handle).clone()
{ overriden_agent.credential.clone() } else { listing.agent.credential.clone() } { overriden_agent.credential.clone() } else { listing.agent.credential.clone() }
} project_id={listing.project.id}/> } project_id={listing.project.id}/>
@ -103,7 +137,7 @@ pub fn details_page(props: &DetailsPageProps) -> Html {
<div class={"details-head"}> <div class={"details-head"}>
<div class={"details-head-title"}>{project_title}</div> <div class={"details-head-title"}>{project_title}</div>
<MediaSlideshow media_list={project_media_list}/> <MediaSlideshow media_list={project_media_list}/>
// Agent // Agent
<div class={"details-head-agent-container"}> <div class={"details-head-agent-container"}>
// Profile pic // Profile pic
@ -111,7 +145,7 @@ pub fn details_page(props: &DetailsPageProps) -> Html {
if let Some(overriden_agent) = (*override_agent_handle).clone() if let Some(overriden_agent) = (*override_agent_handle).clone()
{ overriden_agent.profile_picture_url.clone() } else { listing.agent.profile_picture_url.clone() } { overriden_agent.profile_picture_url.clone() } else { listing.agent.profile_picture_url.clone() }
} alt={"agent_pfp"}/> } alt={"agent_pfp"}/>
<div class={"details-head-agent-text-container"}> <div class={"details-head-agent-text-container"}>
<div class={"details-head-agent-heading"}>{ <div class={"details-head-agent-heading"}>{
if let Some(overriden_agent) = (*override_agent_handle).clone() if let Some(overriden_agent) = (*override_agent_handle).clone()
@ -125,11 +159,11 @@ pub fn details_page(props: &DetailsPageProps) -> Html {
}</div> }</div>
</div> </div>
</div> </div>
<div class={"details-body-divider"}></div> <div class={"details-body-divider"}></div>
</div> </div>
<div class={"details-body"}> <div class={"details-body"}>
<div class={"details-body-description"}> <div class={"details-body-description"}>
@ -150,10 +184,10 @@ pub fn details_page(props: &DetailsPageProps) -> Html {
<FeatureItem title={"Tipo de Proyecto"} icon={"fa-solid fa-house-chimney"} subtitle={project_type}/> <FeatureItem title={"Tipo de Proyecto"} icon={"fa-solid fa-house-chimney"} subtitle={project_type}/>
<FeatureItem title={"Cant. de Pisos"} icon={"fa-solid fa-building"} subtitle={project_floors}/> <FeatureItem title={"Cant. de Pisos"} icon={"fa-solid fa-building"} subtitle={project_floors}/>
</div> </div>
<div class={"details-body-divider"}></div> <div class={"details-body-divider"}></div>
// //
// Units part // Units part
// //
@ -163,7 +197,7 @@ pub fn details_page(props: &DetailsPageProps) -> Html {
html! { html! {
<div class={"details-body-units"}> <div class={"details-body-units"}>
<div class={"details-body-units-title"}>{"Unidades"}</div> <div class={"details-body-units-title"}>{"Unidades"}</div>
{create_unit_selection_menu(&organized_units, selected_unit_handle)} {create_unit_selection_menu(&organized_units, selected_unit_handle)}
{ {
@ -193,8 +227,8 @@ pub fn details_page(props: &DetailsPageProps) -> Html {
html! {} html! {}
} }
} }
<div class={"details-body-units-media-slideshow-frame"}> <div class={"details-body-units-media-slideshow-frame"}>
{ {
@ -209,7 +243,7 @@ pub fn details_page(props: &DetailsPageProps) -> Html {
} }
} }
</div> </div>
<div class={"details-body-units-description"}> <div class={"details-body-units-description"}>
{ {
if unit.description.len() == 0 { if unit.description.len() == 0 {
@ -225,11 +259,11 @@ pub fn details_page(props: &DetailsPageProps) -> Html {
{"Aplica para financiamiento"} {"Aplica para financiamiento"}
</a> </a>
</div> </div>
<div class={"details-body-divider"}></div> <div class={"details-body-divider"}></div>
<div class={"details-body-disclaimer"}> <div class={"details-body-disclaimer"}>
{"*Unidades sujetas a disponibilidad"} {"*Unidades sujetas a disponibilidad"}
@ -247,7 +281,7 @@ pub fn details_page(props: &DetailsPageProps) -> Html {
} }
} }
} }
</div> </div>
</div> </div>
} }
@ -259,8 +293,8 @@ pub fn details_page(props: &DetailsPageProps) -> Html {
} }
} }
} }
</div> </div>
{ {
if *finished_loading { if *finished_loading {
@ -269,14 +303,14 @@ pub fn details_page(props: &DetailsPageProps) -> Html {
html! {} 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
@ -302,9 +338,9 @@ pub fn create_unit_selection_menu(units: &Vec<&Unit>, selected_unit_handle: UseS
}; };
html! { html! {
<div class={ <div class={
if *cloned_selected_unit_handle == index if *cloned_selected_unit_handle == index
{"details-body-units-selection-item-selected"} {"details-body-units-selection-item-selected"}
else else
{"details-body-units-selection-item-unselected"} {"details-body-units-selection-item-unselected"}
} onclick={select_unit_onclick_cb}> } onclick={select_unit_onclick_cb}>
{ {
@ -323,13 +359,13 @@ pub fn create_unit_selection_menu(units: &Vec<&Unit>, selected_unit_handle: UseS
} }
} }
} }
</div> </div>
} }
}).collect::<Html>() }).collect::<Html>()
} }
</div> </div>
} }
} }

View File

@ -1,20 +1,21 @@
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
// Main section // Main section
<div class={"lp-main-cover-slide"}> <div class={"lp-main-cover-slide"}>
<div class={"lp-main-cover-slide-text-container"}> <div class={"lp-main-cover-slide-text-container"}>
@ -27,7 +28,7 @@ pub fn landing_page() -> Html {
<div class={"lp-main-cover-slide-art-detail"}/> <div class={"lp-main-cover-slide-art-detail"}/>
</div> </div>
</div> </div>
// Second section // Second section
<div class={"lp-second-cover-slide-container"}> <div class={"lp-second-cover-slide-container"}>
<div class={"lp-second-cover-slide"}> <div class={"lp-second-cover-slide"}>
@ -74,12 +75,12 @@ pub fn landing_page() -> Html {
<div class={"lp-testimonials-section-card-subtitle"}>{"Santo Domingo"}</div> <div class={"lp-testimonials-section-card-subtitle"}>{"Santo Domingo"}</div>
<div class={"lp-testimonials-section-card-description"}>{"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fames ut dictumst urna, lorem nibh. Pretium leo hendrerit interdum netus"}</div> <div class={"lp-testimonials-section-card-description"}>{"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fames ut dictumst urna, lorem nibh. Pretium leo hendrerit interdum netus"}</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<PageFooter/> <PageFooter/>
</> </>
} }
} }

View File

@ -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;

View File

@ -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 {
@ -13,4 +13,4 @@ pub fn not_found_page() -> Html {
<PageFooter/> <PageFooter/>
</> </>
} }
} }

View File

@ -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 {
@ -16,7 +28,7 @@ pub fn search_page() -> Html {
let page_counter: UseStateHandle<i64> = use_state(|| 1); let page_counter: UseStateHandle<i64> = use_state(|| 1);
let finished_loading = use_state(|| false); let finished_loading = use_state(|| false);
let project_state_filter_handle = use_state_eq(|| ProjectState::InConstruction); let project_state_filter_handle = use_state_eq(|| ProjectState::InConstruction);
// All code to execute on first render and never again // All code to execute on first render and never again
use_state(|| { use_state(|| {
let cities_handle = cities_handle.clone(); let cities_handle = cities_handle.clone();
@ -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()),
}); });
/* /*
@ -87,19 +118,24 @@ 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 =
initial: OptionWrapper::new(None), comp_with::<DropDown<OptionWrapper<ProjectCondition>>>(DropDownProps {
options: vec![OptionWrapper::new(None), OptionWrapper::new(Some(ProjectCondition::New)), OptionWrapper::new(Some(ProjectCondition::Resale)) ], initial: OptionWrapper::new(None),
selection_changed: { options: vec![
let cloned_project_condition_filter = project_condition_filter.clone(); OptionWrapper::new(None),
Callback::from(move |project_condition: OptionWrapper<ProjectCondition>| { OptionWrapper::new(Some(ProjectCondition::New)),
info!("{}", project_condition.to_string()); OptionWrapper::new(Some(ProjectCondition::Resale)),
cloned_project_condition_filter.set(project_condition) ],
} selection_changed: {
)}, let cloned_project_condition_filter = project_condition_filter.clone();
class_css: Some("project-search-filter-item".into()) 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 { let project_city_drop_down = comp_with::<DropDown<OptionWrapper<String>>>(DropDownProps {
initial: OptionWrapper::new(None), initial: OptionWrapper::new(None),
@ -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"}>
@ -223,7 +273,7 @@ pub fn search_page() -> Html {
{"Estado del Proyecto"} {"Estado del Proyecto"}
</div> </div>
<div class={"project-search-filter-select"}> <div class={"project-search-filter-select"}>
<div class={if (*project_state_filter_handle) == ProjectState::InConstruction { <div class={if (*project_state_filter_handle) == ProjectState::InConstruction {
"project-search-filter-select-item-selected" "project-search-filter-select-item-selected"
} else { } else {
@ -261,7 +311,7 @@ pub fn search_page() -> Html {
</div> </div>
{project_condition_drop_down} {project_condition_drop_down}
</div> </div>
<div class={"project-search-filter-container"}> <div class={"project-search-filter-container"}>
<div class={"project-search-filter-label"}> <div class={"project-search-filter-label"}>
{"Ciudad"} {"Ciudad"}
@ -295,10 +345,10 @@ pub fn search_page() -> Html {
html!{ html!{
<div class={"project-search-results-container"}> // Search Results Content <div class={"project-search-results-container"}> // Search Results Content
{if (*search_results_handle).len() == 0 { {if (*search_results_handle).len() == 0 {
html!{ html!{
<div style={"margin-bottom: 40vh"}> <div style={"margin-bottom: 40vh"}>
{"No se han encontrado proyectos con los filtros de busqueda especificados."} {"No se han encontrado proyectos con los filtros de busqueda especificados."}
</div> </div>
} }
} else { } else {
html! {} html! {}
@ -311,11 +361,11 @@ pub fn search_page() -> Html {
<div class={"project-search-results-container"}> // Search Results Content <div class={"project-search-results-container"}> // Search Results Content
<div class="lds-facebook"><div></div><div></div><div></div></div> <div class="lds-facebook"><div></div><div></div><div></div></div>
</div> </div>
} }
} }
} }
</div> </div>
{ {
if *finished_loading { if *finished_loading {

View File

@ -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}/> },
} }
} }

View File

@ -1 +1 @@
pub mod main_router; pub mod main_router;

View File

@ -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
}, }
} }
} }

View File

@ -0,0 +1 @@

View File

@ -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();
@ -13,4 +13,4 @@ pub fn get_value_from_textarea_event(e: InputEvent) -> String {
let event_target = event.target().unwrap_throw(); let event_target = event.target().unwrap_throw();
let target: HtmlTextAreaElement = event_target.dyn_into().unwrap_throw(); let target: HtmlTextAreaElement = event_target.dyn_into().unwrap_throw();
target.value() target.value()
} }

View File

@ -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;

View File

@ -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 {
@ -52,4 +52,4 @@ pub fn get_from_local_storage(key: StorageKey) -> Result<Option<String>, Error>
}, },
None => Err(Error::IO(MessageResource::new_from_string(format!("Error accessing Window object.")))), None => Err(Error::IO(MessageResource::new_from_string(format!("Error accessing Window object.")))),
} }
} }