This commit is contained in:
Franklin 2023-04-15 20:37:35 -04:00
parent 33f55a04a2
commit c693287063
14 changed files with 406 additions and 14 deletions

0
css/admin-agents.css Normal file
View File

0
css/admin-contacts.css Normal file
View File

9
css/admin-projects.css Normal file
View File

@ -0,0 +1,9 @@
.admin-projects-table {
display: flex;
flex-direction: column;
justify-content: center;
align-items: space-evenly;
gap: 5px;
width: 90%;
}

View File

@ -0,0 +1,60 @@
.admin-project-container {
display: flex;
flex-direction: row;
justify-content: space-evenly;
align-items: center;
gap: 5px;
height: 40px;
width: 100%;
}
.admin-project-container:hover {
cursor: pointer;
background-color: #02104a51;
border-radius: 3px;
transition-duration: 0.3s;
}
.admin-project-trash-bin {
display: flex;
justify-content: center;
align-items: center;
font-size: 13px;
width: 25px;
height: 25px;
}
.admin-project-trash-bin-selected {
display: flex;
justify-content: center;
align-items: center;
border-radius: 3px;
font-size: 13px;
background-color: #e32;
color: white;
width: 25px;
height: 25px;
}
.admin-project-trash-bin:hover {
cursor: pointer;
}
.admin-project-location {
font-size: 13px;
font-family: Source Sans Pro;
font-weight: 100;
text-align: start;
width: 150px;
}
.admin-project-column {
font-size: 13px;
font-family: Source Sans Pro;
font-weight: 100;
text-align: start;
width: 100px;
}
.admin-project-index {
font-size: 13px;
font-family: Source Sans Pro;
font-weight: 100;
text-align: start;
width: 15px;
}

24
css/edit.css Normal file
View File

@ -0,0 +1,24 @@
.admin-project-fields-container {
display: flex;
flex-direction: column;
justify-content: start;
align-items: center;
gap: 20px;
width: 90%;
}
.admin-project-field-container {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
width: 100%;
}
.admin-project-field-label {
font-family: Source Sans Pro;
font-size: 16px;
text-align: center;
}

View File

@ -15,6 +15,10 @@
<link data-trunk type="text/css" href="css/admin-login.css" rel="css" />
<link data-trunk type="text/css" href="css/admin-start.css" rel="css" />
<link data-trunk type="text/css" href="css/admin-common.css" rel="css" />
<link data-trunk type="text/css" href="css/admin-projects.css" rel="css" />
<link data-trunk type="text/css" href="css/admin-agents.css" rel="css" />
<link data-trunk type="text/css" href="css/admin-contacts.css" rel="css" />
<link data-trunk type="text/css" href="css/edit.css" rel="css" />
<link data-trunk type="text/css" href="css/components/loading.css" rel="css" />
@ -23,6 +27,7 @@
<link data-trunk type="text/css" href="css/components/footer.css" rel="css" />
<link data-trunk type="text/css" href="css/components/agent_card.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 href="https://fonts.googleapis.com/css?family=Source+Sans+Pro" rel="stylesheet">

View File

@ -0,0 +1,62 @@
use jl_types::{dto::project_card::ProjectCardDto, domain::project_state::ProjectState};
use yew::prelude::*;
use yew_router::{prelude::use_navigator};
use crate::{routes::main_router::Route, pages::admin::edit::{EditType, EditItem}};
#[function_component(AdminProject)]
pub fn admin_project(props: &AdminProjectProps) -> Html {
let navigator = use_navigator().unwrap();
let location_formatted = format!("{}, {}", props.project.city, props.project.district);
let is_attempting_delete = use_state(|| false);
let delete_project = {
let is_attempting_delete = is_attempting_delete.clone();
Callback::from(move |event: MouseEvent| {
if *is_attempting_delete {
// Call delete
is_attempting_delete.set(false);
} else {
is_attempting_delete.set(true);
}
event.stop_propagation();
})
};
let onclick_item = {
let props = props.clone();
Callback::from(move |_| {
navigator.push(&Route::AdminEdit { edit_type: EditType::Existing(props.project.id), edit_item: EditItem::Project });
})
};
html! {
<div class={"admin-project-container"} onclick={onclick_item}>
<div class={"admin-project-index"}>
{props.index + 1}
</div>
<div class={"admin-project-location"}>
{location_formatted}
</div>
<div class={"admin-project-column"}>
{props.project.project_type}
</div>
<div class={"admin-project-column"}>
{props.project.project_condition.clone()}
</div>
<div class={"admin-project-column"}>
{match props.project.project_state {
ProjectState::InConstruction => "En Construcción",
ProjectState::Finished => "Terminado",
}}
</div>
<div class={if *is_attempting_delete {"admin-project-trash-bin-selected"} else {"admin-project-trash-bin"}} onclick={delete_project}>
<i class="fa-regular fa-trash-can"></i>
</div>
</div>
}
}
#[derive(PartialEq, Properties, Clone)]
pub struct AdminProjectProps {
pub project: ProjectCardDto,
pub index: usize,
}

View File

@ -5,4 +5,5 @@ 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;

View File

@ -1,15 +1,30 @@
use yew::prelude::*;
use crate::components::admin_nav_bar::AdminNavigationBar;
use crate::{components::admin_nav_bar::AdminNavigationBar, api::backend::get_all_agents};
#[function_component(AdminAgents)]
pub fn admin_agents() -> Html {
let agents = use_state(|| Vec::new());
use_state(|| {
let agents_handle = agents.clone();
wasm_bindgen_futures::spawn_local(async move {
match get_all_agents().await {
Ok(agents) => {
agents_handle.set(agents);
},
Err(error) => log::error!("Error retrieving agents from backend. Error: {}", error),
};
});
});
html! {
<>
<AdminNavigationBar/>
<div class={"page-container"}>
<div class={"admin-page-container"}>
<div class={"admin-start-container"}>
{""}
<div class={"admin-panel-page-title"}>{"Agentes"}</div>
</div>
</div>
</>

View File

@ -1,15 +1,29 @@
use yew::prelude::*;
use crate::components::admin_nav_bar::AdminNavigationBar;
use crate::{components::admin_nav_bar::AdminNavigationBar, api::backend::get_all_contacts};
#[function_component(AdminContacts)]
pub fn admin_contacts() -> Html {
let contacts = use_state(|| Vec::new());
use_state(|| {
let contacts_handle = contacts.clone();
wasm_bindgen_futures::spawn_local(async move {
match get_all_contacts().await {
Ok(contacts) => {
contacts_handle.set(contacts);
},
Err(error) => log::error!("Error retrieving contacts from backend. Error: {}", error),
};
});
});
html! {
<>
<AdminNavigationBar/>
<div class={"page-container"}>
<div class={"admin-page-container"}>
<div class={"admin-start-container"}>
{""}
<div class={"admin-panel-page-title"}>{"Solicitudes de Contacto"}</div>
</div>
</div>
</>

174
src/pages/admin/edit.rs Normal file
View File

@ -0,0 +1,174 @@
use std::{fmt::Display, str::FromStr};
use chrono::Utc;
use jl_types::{ domain::{agent::Agent, project_state::ProjectState, project_condition::ProjectCondition, project_type::ProjectType, media::{Media, MediaList}, unit::Unit}, dto::listing::Listing};
use uuid::Uuid;
use yew::prelude::*;
use yew_router::prelude::use_navigator;
use crate::{components::admin_nav_bar::AdminNavigationBar, api::backend::get_project_listing};
/// 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)]
pub fn edit_page(props: &AdminEditPageProps) -> Html {
let navigator = use_navigator().unwrap();
let listing = use_state(|| None);
use_state(|| {
let listing = listing.clone();
match props.edit_item {
EditItem::Agent => {
},
EditItem::Project => {
match props.edit_type {
EditType::New => {},
EditType::Existing(uid) => {
wasm_bindgen_futures::spawn_local(async move {
let listing_result = get_project_listing(&uid).await;
match listing_result {
Ok(listing_persisted) => {
listing.set(Some(listing_persisted));
},
Err(error) => log::error!("Error loading listing: {error}")
}
});
},
};
},
EditItem::Unit(_) => {},
}});
html! {
<>
<AdminNavigationBar/>
<div class={"admin-page-container"}>
<div class={"admin-start-container"}>
<div class={"admin-panel-page-title"}>{format!("Editar {}", match props.edit_item {
EditItem::Agent => "Agente",
EditItem::Project => "Proyecto",
EditItem::Unit(_) => "Unidad",
})}</div>
{
match props.edit_item {
EditItem::Agent => {html! {}},
EditItem::Project => {html! { <ProjectFields listing={(*listing).clone()}/> }},
EditItem::Unit(_) => {html! {}},
}
}
</div>
</div>
</>
}
}
#[derive(PartialEq, Clone)]
pub enum EditItem {
Agent,
Project,
/// Project Uid
Unit(Uuid),
}
#[derive(PartialEq, Clone)]
pub enum EditType {
New, Existing(Uuid)
}
#[derive(Properties, PartialEq)]
pub struct AdminEditPageProps {
pub edit_item: EditItem,
pub edit_type: EditType,
}
#[derive(Properties, PartialEq, Clone)]
pub struct ProjectFieldsProps {
pub listing: Option<Listing>
}
#[allow(unused)]
#[function_component(ProjectFields)]
pub fn generate_fields_for_project(props: &ProjectFieldsProps) -> Html {
let listing_opt = props.listing.clone();
let location_city = use_state(|| listing_opt.clone().unwrap_or_default().location.city);
let location_district = use_state(|| listing_opt.clone().unwrap_or_default().location.district);
let agent: UseStateHandle<Option<Agent>> = use_state(|| {
match listing_opt.clone() {
Some(listing) => Some(listing.agent),
None => None,
}
});
let project_state = use_state(|| listing_opt.clone().unwrap_or_default().project.project_state);
let project_condition = use_state(|| listing_opt.clone().unwrap_or_default().project.project_condition);
let project_type = use_state(|| listing_opt.clone().unwrap_or_default().project.project_type);
let project_description = use_state(|| listing_opt.clone().unwrap_or_default().project.description);
let project_admin_tag: UseStateHandle<Option<String>> = use_state(|| listing_opt.clone().unwrap_or_default().project.admin_tag);
let project_finish_date = use_state(|| listing_opt.clone().unwrap_or_default().project.finish_date);
let project_floors = use_state(|| listing_opt.clone().unwrap_or_default().project.floors);
let media: UseStateHandle<MediaList> = use_state(|| listing_opt.clone().unwrap_or_default().project.media);
let units: UseStateHandle<Vec<Unit>> = use_state(|| listing_opt.clone().unwrap_or_default().units);
html! {
}
}
impl Display for EditItem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EditItem::Agent => write!(f, "agent"),
EditItem::Project => write!(f, "project"),
EditItem::Unit(id) => write!(f, "unit{id}"),
}
}
}
impl Display for EditType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EditType::New => write!(f, "new"),
EditType::Existing(id) => {
let fmt_id = id.to_string().replace("-", "");
write!(f, "existing{fmt_id}")
},
}
}
}
impl FromStr for EditItem {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.starts_with("unit") {
let (_, p_id) = s.split_at(4);
let uid: Result<Uuid, _> = p_id.try_into();
if let Ok(id) = uid {
return Ok(Self::Unit(id));
}
}
match s {
"agent" => Ok(Self::Agent),
"project" => Ok(Self::Project),
_ => Err(())
}
}
}
impl FromStr for EditType {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.starts_with("existing") {
let existing = s.replace("-", "");
let (_, p_id) = existing.split_at(8);
let uid = p_id.to_string();
let uid: Result<Uuid, _> = uid.as_str().try_into();
if let Ok(id) = uid {
return Ok(Self::Existing(id));
}
}
match s {
"new" => Ok(Self::New),
_ => Err(())
}
}
}

View File

@ -2,4 +2,5 @@ pub mod login;
pub mod start;
pub mod projects;
pub mod contacts;
pub mod agents;
pub mod agents;
pub mod edit;

View File

@ -1,15 +1,41 @@
use yew::prelude::*;
use crate::components::admin_nav_bar::AdminNavigationBar;
use crate::{components::{admin_nav_bar::AdminNavigationBar, admin_project::AdminProject}, api::backend::get_all_projects_with_filters_paged};
#[function_component(AdminProjects)]
pub fn admin_projects() -> Html {
let projects = use_state(|| Vec::new());
use_state(|| {
let projects_handle = projects.clone();
wasm_bindgen_futures::spawn_local(async move {
match get_all_projects_with_filters_paged(&1, Vec::new()).await {
Ok(projects) => {
projects_handle.set(projects);
},
Err(error) => log::error!("Error retrieving projects from backend. Error: {}", error),
};
});
});
html! {
<>
<AdminNavigationBar/>
<div class={"page-container"}>
<div class={"admin-page-container"}>
<div class={"admin-start-container"}>
{""}
<div class={"admin-panel-page-title"}>{"Proyectos"}</div>
<div class={"admin-projects-table"}>
<div class={"admin-navbar-divider"}></div>
{(*projects).clone().into_iter().enumerate().map(|(key, project)| html! {
<>
<AdminProject project={project} index={key}/>
<div class={"admin-navbar-divider"}></div>
</>
}).collect::<Html>()}
</div>
</div>
</div>
</>

View File

@ -3,7 +3,7 @@ use yew::prelude::*;
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}, agents::AgentsPage}};
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}};
#[derive(Clone, Routable, PartialEq)]
pub enum Route {
@ -28,6 +28,8 @@ pub enum Route {
AdminAgents,
#[at("/admin/contacts")]
AdminContacts,
#[at("/admin/edit/:edit_type/:edit_item")]
AdminEdit { edit_type: EditType, edit_item: EditItem },
#[not_found]
#[at("/404")]
@ -48,7 +50,6 @@ pub fn switch(routes: Route) -> Html {
Route::AdminProjects => html! { <AdminProjects/> },
Route::AdminAgents => html! { <AdminAgents/> },
Route::AdminContacts => html! { <AdminContacts/> },
Route::AdminEdit { edit_type, edit_item } => html! { <AdminEditPage edit_item={edit_item} edit_type={edit_type}/> },
}
}