diff --git a/css/components/loading.css b/css/components/loading.css new file mode 100644 index 0000000..4122984 --- /dev/null +++ b/css/components/loading.css @@ -0,0 +1,43 @@ +.loading-container{ + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 50vh; +}.lds-facebook { + display: inline-block; + position: relative; + width: 80px; + height: 80px; + } + .lds-facebook div { + display: inline-block; + position: absolute; + left: 8px; + width: 16px; + background: #5D6A73; + animation: lds-facebook 1.2s cubic-bezier(0, 0.5, 0.5, 1) infinite; + } + .lds-facebook div:nth-child(1) { + left: 8px; + animation-delay: -0.24s; + } + .lds-facebook div:nth-child(2) { + left: 32px; + animation-delay: -0.12s; + } + .lds-facebook div:nth-child(3) { + left: 56px; + animation-delay: 0; + } + @keyframes lds-facebook { + 0% { + top: 8px; + height: 64px; + } + 50%, 100% { + top: 24px; + height: 32px; + } + } + \ No newline at end of file diff --git a/css/components/media_slideshow.css b/css/components/media_slideshow.css new file mode 100644 index 0000000..690b39c --- /dev/null +++ b/css/components/media_slideshow.css @@ -0,0 +1,83 @@ + +.media-slideshow-frame { + height: 360px; + background-color: gray; + width: 100%; + position: relative; +} + +.media-slideshow-photo { + background-color: gray; + object-fit: cover; + width: 100%; + height: 100%; + display: none; +} + +.media-slideshow-video { + background-color: gray; + width: 100%; + height: 100%; + display: none; + object-fit: cover; +} + +.media-slideshow-arrow-left { + cursor: pointer; + position: absolute; + top: 50%; + width: auto; + margin-top: -22px; + padding: 16px; + color: white; + font-weight: bold; + font-size: 18px; + transition: 0.6s ease; + border-radius: 0 3px 3px 0; + user-select: none; + transition: 0.6s ease; +} +.media-slideshow-arrow-right { + cursor: pointer; + position: absolute; + top: 50%; + width: auto; + margin-top: -22px; + padding: 16px; + color: white; + font-weight: bold; + font-size: 18px; + transition: 0.6s ease; + border-radius: 0 3px 3px 0; + user-select: none; + right: 0; + border-radius: 3px 0 0 3px; + transition: 0.6s ease; +} + +.media-slideshow-arrow-left:hover, .media-slideshow-arrow-right:hover { + background-color: rgba(0,0,0,0.8); +} + +/* Number text (1/3 etc) */ +.media-slideshow-number-text { + color: #f2f2f2; + font-size: 12px; + padding: 8px 12px; + position: absolute; + top: 0; + background-color: rgba(0,0,0,0.8); + border-radius: 2px; + } + + +/* Fading animation */ +.fade { + animation-name: fade; + animation-duration: 1.5s; + } + + @keyframes fade { + from {opacity: .4} + to {opacity: 1} + } \ No newline at end of file diff --git a/css/details.css b/css/details.css index 6d08a4f..cfd8902 100644 --- a/css/details.css +++ b/css/details.css @@ -18,15 +18,10 @@ Divide the Details page into 3 main sections: justify-content: stretch; align-items: center; width: 100%; -} - -.details-head-image-frame { - display: flex; - height: 300px; - width: 100%; - background-color: gray; + gap: 10px; } .details-head-title { font-size: 25px; -} \ No newline at end of file +} + diff --git a/css/navbar.css b/css/navbar.css index f3a157d..d3f40a7 100644 --- a/css/navbar.css +++ b/css/navbar.css @@ -2,6 +2,7 @@ .navbar-background { border-top: 3px solid #04B2D9; position: fixed; + z-index: 1; width: 100%; display: flex; flex-direction: row; @@ -42,7 +43,7 @@ cursor: pointer; } -.navbar-closed{ +.navbar-closed { display: flex; flex-direction: column; justify-content: flex-start; diff --git a/index.html b/index.html index 739fc65..32cfd1a 100644 --- a/index.html +++ b/index.html @@ -10,7 +10,8 @@ - + + diff --git a/src/components/media_slideshow.rs b/src/components/media_slideshow.rs new file mode 100644 index 0000000..08cd2c5 --- /dev/null +++ b/src/components/media_slideshow.rs @@ -0,0 +1,72 @@ +use jl_types::domain::media::Media; +use yew::prelude::*; + + +#[function_component(MediaSlideshow)] +pub fn media_slideshow(props: &MediaSlideshowProps) -> Html { + let current_media_index = use_state(|| 0); + let total_media_count = props.media_list.len(); + let previous_click_cb = { + let current_media_index = current_media_index.clone(); + let total_media_count = total_media_count.clone(); + Callback::from(move |_| { + if *current_media_index > 0 { + current_media_index.set(*current_media_index -1); + } else { + current_media_index.set(total_media_count - 1); + } + }) + }; + let next_click_cb = { + let current_media_index = current_media_index.clone(); + let total_media_count = total_media_count.clone(); + Callback::from(move |_| { + if *current_media_index >= total_media_count - 1 { + current_media_index.set(0); + } else { + current_media_index.set(*current_media_index + 1); + } + }) + }; + html! { +
+ { + props.media_list.iter().enumerate().map(|(index, media)| { + match media { + Media::Photo(url) => html! { + + }, + Media::Video(url) => html! { + + } + } + }).collect::() + } + +
+ +
+ +
+ +
+ +
{format!("{}/{}", *current_media_index + 1, total_media_count)}
+
+ } +} + +#[derive(Properties, PartialEq, PartialOrd)] +pub struct MediaSlideshowProps { + pub media_list: Vec, +} \ No newline at end of file diff --git a/src/components/mod.rs b/src/components/mod.rs index d5e08a5..330b859 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -1,3 +1,4 @@ pub mod nav_bar; pub mod search_filter; -pub mod project_card; \ No newline at end of file +pub mod project_card; +pub mod media_slideshow; \ No newline at end of file diff --git a/src/pages/details.rs b/src/pages/details.rs index 780d85c..cbd5f9f 100644 --- a/src/pages/details.rs +++ b/src/pages/details.rs @@ -1,25 +1,80 @@ +use jl_types::dto::listing::Listing; +use log::{error, info}; use uuid::Uuid; use yew::prelude::*; -use crate::components::nav_bar::NavigationBar; +use crate::{components::{nav_bar::NavigationBar, media_slideshow::MediaSlideshow}, api::backend::get_project_listing}; #[function_component(DetailsPage)] -pub fn details_page(_props: &DetailsPageProps) -> Html { +pub fn details_page(props: &DetailsPageProps) -> Html { + //TODO: query backend + let finished_loading = use_state(|| false); + let listing_handle: UseStateHandle = use_state(|| Listing::default()); + use_state(|| { + let finished_loading = finished_loading.clone(); + let listing_handle = listing_handle.clone(); + let project_id = props.project_id.clone(); + wasm_bindgen_futures::spawn_local(async move { + match get_project_listing(&project_id).await { + Ok(listing) => { + listing_handle.set(listing); + finished_loading.set(true); + info!("Finshed loading listing"); + }, + Err(error) => { + error!("Error fetching listing. Error: {:?}", error); + }, + }; + }); + }); + + let listing = (*listing_handle).clone(); + + let project_title = format!("{} en {}, {}", &listing.project.project_type, &listing.location.district, &listing.location.city); + let project_description = &listing.project.description; + let project_media_list = listing.project.media.media_list; + /*let project_price = format!("Desde ${} USD", match listing.project.starts_from { + Some(price) => { + let price_separated = price.separate_with_commas(); + if price_separated.contains(".") { + price_separated + } else { + format!("{price_separated}.00") + } + }, + None => "0.00".into() + });*/ + + let project_condition = &listing.project.project_condition.to_string(); + + let project_est_finish_date = &listing.project.finish_date.format("%m/%Y"); + html!{ <>
-
-
-
- -
-
{"Suites by refa piantini"}
-
{"RD$123,130.00"}
-
{"Andres Julio Aybar #39"}
-
{"DescripciĆ³n"}
-
-
+ { + if *finished_loading { + html!{ +
+
+ +
{project_title}
+
{"RD$123,130.00"}
+
{project_description}
+
+
+ } + } else { + html!{ +
+
+
+ } + } + } + +
} diff --git a/src/pages/search.rs b/src/pages/search.rs index 3b4a969..1dcb78b 100644 --- a/src/pages/search.rs +++ b/src/pages/search.rs @@ -14,7 +14,8 @@ pub fn search_page(props: &SearchPageProperties) -> Html { let districts_handle = use_state(|| Vec::from([OptionWrapper::new(None)])); let search_results_handle: UseStateHandle> = use_state(|| Vec::new()); let page_counter: UseStateHandle = use_state(|| 1); - + let finished_loading = use_state(|| false); + let mut filters = Vec::new(); if props.project_state.eq(&ProjectState::Finished) { filters.push(Filter::Finished); @@ -25,6 +26,7 @@ pub fn search_page(props: &SearchPageProperties) -> Html { let cities_handle = cities_handle.clone(); let search_results_handle = search_results_handle.clone(); let page_counter = page_counter.clone(); + let finished_loading = finished_loading.clone(); wasm_bindgen_futures::spawn_local(async move { match get_all_cities().await { Ok(cities) => { @@ -36,7 +38,8 @@ pub fn search_page(props: &SearchPageProperties) -> Html { }; match get_all_projects_with_filters_paged(&(*page_counter), filters).await { Ok(projects) => { - search_results_handle.set(projects) + search_results_handle.set(projects); + finished_loading.set(true); }, Err(error) => info!("Error in loading projects: {error}"), }; @@ -233,9 +236,23 @@ pub fn search_page(props: &SearchPageProperties) -> Html {
-
// Search Results Content - {(*search_results_handle).clone().into_iter().map(|project| html!{}).collect::()} -
+ { + if *finished_loading { + html!{ +
// Search Results Content + {(*search_results_handle).clone().into_iter().map(|project| html!{}).collect::()} +
+ } + } else { + html!{ +
// Search Results Content +
+
+ + } + } + } + }