Merge branch 'master' of gitlab.com:adrian.wozniak/jirs
This commit is contained in:
commit
5d335a1ed9
@ -56,6 +56,12 @@ aside#navbar-left .item > .styledIcon {
|
|||||||
line-height: 42px;
|
line-height: 42px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
aside#navbar-left .item > .styledIcon > .styledAvatar {
|
||||||
|
margin-top: 7px;
|
||||||
|
width: 27px;
|
||||||
|
height: 27px;
|
||||||
|
}
|
||||||
|
|
||||||
aside#navbar-left > .item > .styledIcon.search {
|
aside#navbar-left > .item > .styledIcon.search {
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
color: #DEEBFE;
|
color: #DEEBFE;
|
||||||
|
@ -13,6 +13,7 @@ mod api;
|
|||||||
mod invite;
|
mod invite;
|
||||||
mod modal;
|
mod modal;
|
||||||
mod model;
|
mod model;
|
||||||
|
mod profile;
|
||||||
mod project;
|
mod project;
|
||||||
mod project_settings;
|
mod project_settings;
|
||||||
mod shared;
|
mod shared;
|
||||||
@ -37,6 +38,7 @@ pub enum FieldId {
|
|||||||
SignUp(SignUpFieldId),
|
SignUp(SignUpFieldId),
|
||||||
Invite(InviteFieldId),
|
Invite(InviteFieldId),
|
||||||
Users(UsersFieldId),
|
Users(UsersFieldId),
|
||||||
|
Profile(UsersFieldId),
|
||||||
// issue
|
// issue
|
||||||
AddIssueModal(IssueFieldId),
|
AddIssueModal(IssueFieldId),
|
||||||
EditIssueModal(EditIssueModalSection),
|
EditIssueModal(EditIssueModalSection),
|
||||||
@ -127,6 +129,11 @@ impl std::fmt::Display for FieldId {
|
|||||||
UsersFieldId::Email => f.write_str("users-email"),
|
UsersFieldId::Email => f.write_str("users-email"),
|
||||||
UsersFieldId::UserRole => f.write_str("users-userRole"),
|
UsersFieldId::UserRole => f.write_str("users-userRole"),
|
||||||
},
|
},
|
||||||
|
FieldId::Profile(sub) => match sub {
|
||||||
|
UsersFieldId::Username => f.write_str("profile-username"),
|
||||||
|
UsersFieldId::Email => f.write_str("profile-email"),
|
||||||
|
UsersFieldId::UserRole => f.write_str("profile-userRole"),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -150,10 +157,16 @@ pub enum ProjectPageChange {
|
|||||||
SubmitForm,
|
SubmitForm,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum ProfilePageChange {
|
||||||
|
SubmitForm,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum PageChanged {
|
pub enum PageChanged {
|
||||||
Users(UsersPageChange),
|
Users(UsersPageChange),
|
||||||
ProjectSettings(ProjectPageChange),
|
ProjectSettings(ProjectPageChange),
|
||||||
|
Profile(ProfilePageChange),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
@ -260,6 +273,7 @@ fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
|
|||||||
Page::SignUp => sign_up::update(msg, model, orders),
|
Page::SignUp => sign_up::update(msg, model, orders),
|
||||||
Page::Invite => invite::update(msg, model, orders),
|
Page::Invite => invite::update(msg, model, orders),
|
||||||
Page::Users => users::update(msg, model, orders),
|
Page::Users => users::update(msg, model, orders),
|
||||||
|
Page::Profile => profile::update(msg, model, orders),
|
||||||
}
|
}
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
// debug!(model);
|
// debug!(model);
|
||||||
@ -275,6 +289,7 @@ fn view(model: &model::Model) -> Node<Msg> {
|
|||||||
Page::SignUp => sign_up::view(model),
|
Page::SignUp => sign_up::view(model),
|
||||||
Page::Invite => invite::view(model),
|
Page::Invite => invite::view(model),
|
||||||
Page::Users => users::view(model),
|
Page::Users => users::view(model),
|
||||||
|
Page::Profile => profile::view(model),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,6 +304,7 @@ fn routes(url: Url) -> Option<Msg> {
|
|||||||
Some(Ok(id)) => Some(Msg::ChangePage(model::Page::EditIssue(id))),
|
Some(Ok(id)) => Some(Msg::ChangePage(model::Page::EditIssue(id))),
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
|
"profile" => Some(Msg::ChangePage(Page::Profile)),
|
||||||
"add-issue" => Some(Msg::ChangePage(Page::AddIssue)),
|
"add-issue" => Some(Msg::ChangePage(Page::AddIssue)),
|
||||||
"project-settings" => Some(Msg::ChangePage(model::Page::ProjectSettings)),
|
"project-settings" => Some(Msg::ChangePage(model::Page::ProjectSettings)),
|
||||||
"login" => Some(Msg::ChangePage(model::Page::SignIn)),
|
"login" => Some(Msg::ChangePage(model::Page::SignIn)),
|
||||||
|
@ -193,6 +193,7 @@ pub enum Page {
|
|||||||
SignUp,
|
SignUp,
|
||||||
Invite,
|
Invite,
|
||||||
Users,
|
Users,
|
||||||
|
Profile,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Page {
|
impl Page {
|
||||||
@ -206,6 +207,7 @@ impl Page {
|
|||||||
Page::SignUp => "/register".to_string(),
|
Page::SignUp => "/register".to_string(),
|
||||||
Page::Invite => "/invite".to_string(),
|
Page::Invite => "/invite".to_string(),
|
||||||
Page::Users => "/users".to_string(),
|
Page::Users => "/users".to_string(),
|
||||||
|
Page::Profile => "/profile".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -358,6 +360,27 @@ impl Default for UsersPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ProfilePage {
|
||||||
|
pub name: StyledInputState,
|
||||||
|
pub email: StyledInputState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProfilePage {
|
||||||
|
pub fn new(user: &User) -> Self {
|
||||||
|
Self {
|
||||||
|
name: StyledInputState::new(
|
||||||
|
FieldId::Profile(UsersFieldId::Username),
|
||||||
|
user.name.as_str(),
|
||||||
|
),
|
||||||
|
email: StyledInputState::new(
|
||||||
|
FieldId::Profile(UsersFieldId::Email),
|
||||||
|
user.email.as_str(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum PageContent {
|
pub enum PageContent {
|
||||||
SignIn(Box<SignInPage>),
|
SignIn(Box<SignInPage>),
|
||||||
@ -366,6 +389,7 @@ pub enum PageContent {
|
|||||||
ProjectSettings(Box<ProjectSettingsPage>),
|
ProjectSettings(Box<ProjectSettingsPage>),
|
||||||
Invite(Box<InvitePage>),
|
Invite(Box<InvitePage>),
|
||||||
Users(Box<UsersPage>),
|
Users(Box<UsersPage>),
|
||||||
|
Profile(Box<ProfilePage>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
68
jirs-client/src/profile.rs
Normal file
68
jirs-client/src/profile.rs
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
use seed::{prelude::*, *};
|
||||||
|
|
||||||
|
use jirs_data::*;
|
||||||
|
|
||||||
|
use crate::api::send_ws_msg;
|
||||||
|
use crate::model::{Model, Page, PageContent, ProfilePage};
|
||||||
|
use crate::shared::styled_field::StyledField;
|
||||||
|
use crate::shared::styled_form::StyledForm;
|
||||||
|
use crate::shared::styled_input::StyledInput;
|
||||||
|
use crate::shared::{inner_layout, ToNode};
|
||||||
|
use crate::{FieldId, Msg};
|
||||||
|
|
||||||
|
pub fn update(msg: Msg, model: &mut crate::model::Model, _orders: &mut impl Orders<Msg>) {
|
||||||
|
let user = match model.user {
|
||||||
|
Some(ref user) => user,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
match msg {
|
||||||
|
Msg::WsMsg(WsMsg::AuthorizeLoaded(..)) | Msg::ChangePage(Page::Profile) => {
|
||||||
|
send_ws_msg(WsMsg::ProjectRequest);
|
||||||
|
model.page_content = PageContent::Profile(Box::new(ProfilePage::new(user)));
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
let _project_page = match &mut model.page_content {
|
||||||
|
PageContent::Profile(profile_page) => profile_page,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view(model: &Model) -> Node<Msg> {
|
||||||
|
let page = match &model.page_content {
|
||||||
|
PageContent::Profile(profile_page) => profile_page,
|
||||||
|
_ => return empty![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let username = StyledInput::build(FieldId::Profile(UsersFieldId::Username))
|
||||||
|
.state(&page.name)
|
||||||
|
.valid(true)
|
||||||
|
.build()
|
||||||
|
.into_node();
|
||||||
|
let username_field = StyledField::build()
|
||||||
|
.label("Username")
|
||||||
|
.input(username)
|
||||||
|
.build()
|
||||||
|
.into_node();
|
||||||
|
|
||||||
|
let email = StyledInput::build(FieldId::Profile(UsersFieldId::Username))
|
||||||
|
.state(&page.email)
|
||||||
|
.valid(true)
|
||||||
|
.build()
|
||||||
|
.into_node();
|
||||||
|
let email_field = StyledField::build()
|
||||||
|
.label("E-Mail")
|
||||||
|
.input(email)
|
||||||
|
.build()
|
||||||
|
.into_node();
|
||||||
|
|
||||||
|
let content = StyledForm::build()
|
||||||
|
.heading("Profile")
|
||||||
|
.add_field(username_field)
|
||||||
|
.add_field(email_field)
|
||||||
|
.build()
|
||||||
|
.into_node();
|
||||||
|
inner_layout(model, "profile", vec![content], empty![])
|
||||||
|
}
|
@ -19,10 +19,6 @@ static TIME_TRACKING_FIBONACCI: &'static str = "Tracking employees’ time carri
|
|||||||
static TIME_TRACKING_HOURLY: &'static str = "Employees may feel intimidated by demands to track their time. Or they could feel that they’re constantly being watched and evaluated. And for overly ambitious managers, employee time tracking may open the doors to excessive micromanaging.";
|
static TIME_TRACKING_HOURLY: &'static str = "Employees may feel intimidated by demands to track their time. Or they could feel that they’re constantly being watched and evaluated. And for overly ambitious managers, employee time tracking may open the doors to excessive micromanaging.";
|
||||||
|
|
||||||
pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
|
pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
|
||||||
if model.user.is_none() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if model.page != Page::ProjectSettings {
|
if model.page != Page::ProjectSettings {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -32,8 +28,10 @@ pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>)
|
|||||||
send_ws_msg(WsMsg::ProjectRequest);
|
send_ws_msg(WsMsg::ProjectRequest);
|
||||||
}
|
}
|
||||||
Msg::ChangePage(Page::ProjectSettings) => {
|
Msg::ChangePage(Page::ProjectSettings) => {
|
||||||
send_ws_msg(WsMsg::ProjectRequest);
|
|
||||||
build_page_content(model);
|
build_page_content(model);
|
||||||
|
if model.user.is_some() {
|
||||||
|
send_ws_msg(WsMsg::ProjectRequest);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Msg::WsMsg(WsMsg::ProjectLoaded(..)) => {
|
Msg::WsMsg(WsMsg::ProjectLoaded(..)) => {
|
||||||
build_page_content(model);
|
build_page_content(model);
|
||||||
@ -41,9 +39,13 @@ pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>)
|
|||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if model.user.is_none() || model.project.is_none() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let page = match &mut model.page_content {
|
let page = match &mut model.page_content {
|
||||||
PageContent::ProjectSettings(page) => page,
|
PageContent::ProjectSettings(page) => page,
|
||||||
_ => return,
|
_ => return error!("bad content type"),
|
||||||
};
|
};
|
||||||
page.project_category_state.update(&msg, orders);
|
page.project_category_state.update(&msg, orders);
|
||||||
page.time_tracking.update(&msg);
|
page.time_tracking.update(&msg);
|
||||||
|
@ -14,20 +14,17 @@ pub fn render(model: &Model) -> Node<Msg> {
|
|||||||
id!["projectInfo"],
|
id!["projectInfo"],
|
||||||
project_icon,
|
project_icon,
|
||||||
div![
|
div![
|
||||||
attrs![At::Class => "projectTexts";],
|
class!["projectTexts"],
|
||||||
div![attrs![At::Class => "projectName";], project.name],
|
div![class!["projectName"], project.name],
|
||||||
div![
|
div![class!["projectCategory"], project.category.to_string()]
|
||||||
attrs![At::Class => "projectCategory";],
|
|
||||||
project.category.to_string()
|
|
||||||
]
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
_ => li![
|
_ => li![
|
||||||
id!["projectInfo"],
|
id!["projectInfo"],
|
||||||
div![
|
div![
|
||||||
attrs![At::Class => "projectTexts";],
|
class!["projectTexts"],
|
||||||
div![attrs![At::Class => "projectName";], ""],
|
div![class!["projectName"], ""],
|
||||||
div![attrs![At::Class => "projectCategory";], ""]
|
div![class!["projectCategory"], ""]
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
@ -40,7 +40,7 @@ pub trait ToNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn divider() -> Node<Msg> {
|
pub fn divider() -> Node<Msg> {
|
||||||
div![attrs![At::Class => "divider"], ""]
|
div![class!["divider"], ""]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inner_layout(
|
pub fn inner_layout(
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use seed::{prelude::*, *};
|
use seed::{prelude::*, *};
|
||||||
|
|
||||||
use crate::model::Model;
|
use crate::model::Model;
|
||||||
|
use crate::shared::styled_avatar::StyledAvatar;
|
||||||
use crate::shared::styled_button::StyledButton;
|
use crate::shared::styled_button::StyledButton;
|
||||||
use crate::shared::styled_icon::Icon;
|
use crate::shared::styled_icon::Icon;
|
||||||
use crate::shared::{styled_tooltip, ToNode};
|
use crate::shared::{styled_tooltip, ToNode};
|
||||||
@ -9,6 +10,19 @@ use crate::Msg;
|
|||||||
pub fn render(model: &Model) -> Vec<Node<Msg>> {
|
pub fn render(model: &Model) -> Vec<Node<Msg>> {
|
||||||
let logo_svg = Node::from_html(include_str!("../../static/logo.svg"));
|
let logo_svg = Node::from_html(include_str!("../../static/logo.svg"));
|
||||||
|
|
||||||
|
let user_icon = match model.user.as_ref() {
|
||||||
|
Some(user) => i![
|
||||||
|
class!["styledIcon"],
|
||||||
|
StyledAvatar::build()
|
||||||
|
.size(27)
|
||||||
|
.name(user.name.as_str())
|
||||||
|
.avatar_url(user.avatar_url.as_ref().cloned().unwrap_or_default())
|
||||||
|
.build()
|
||||||
|
.into_node()
|
||||||
|
],
|
||||||
|
_ => i![attrs![At::Class => format!("styledIcon {}", Icon::Plus)]],
|
||||||
|
};
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
about_tooltip_popup(model),
|
about_tooltip_popup(model),
|
||||||
aside![
|
aside![
|
||||||
@ -24,7 +38,13 @@ pub fn render(model: &Model) -> Vec<Node<Msg>> {
|
|||||||
span![attrs![At::Class => "itemText"], "Create Issue"]
|
span![attrs![At::Class => "itemText"], "Create Issue"]
|
||||||
],
|
],
|
||||||
div![
|
div![
|
||||||
attrs![At::Class => "bottom"],
|
class!["bottom"],
|
||||||
|
div![
|
||||||
|
class!["item"],
|
||||||
|
attrs![At::Href=> "/profile"],
|
||||||
|
user_icon,
|
||||||
|
span![class!["itemText"], "Profile"]
|
||||||
|
],
|
||||||
about_tooltip(model, navbar_left_item(model, "About", Icon::Help)),
|
about_tooltip(model, navbar_left_item(model, "About", Icon::Help)),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@ -33,9 +53,9 @@ pub fn render(model: &Model) -> Vec<Node<Msg>> {
|
|||||||
|
|
||||||
fn navbar_left_item(_model: &Model, text: &str, logo: Icon) -> Node<Msg> {
|
fn navbar_left_item(_model: &Model, text: &str, logo: Icon) -> Node<Msg> {
|
||||||
div![
|
div![
|
||||||
attrs![At::Class => "item"],
|
class!["item"],
|
||||||
i![attrs![At::Class => format!("styledIcon {}", logo)]],
|
i![attrs![At::Class => format!("styledIcon {}", logo)]],
|
||||||
span![attrs![At::Class => "itemText"], text]
|
span![class!["itemText"], text]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,13 +20,13 @@ impl Handler<LoadCurrentProject> for DbExecutor {
|
|||||||
type Result = Result<Project, ServiceErrors>;
|
type Result = Result<Project, ServiceErrors>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: LoadCurrentProject, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: LoadCurrentProject, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::projects::dsl::{id, projects};
|
use crate::schema::projects::dsl::projects;
|
||||||
let conn = &self
|
let conn = &self
|
||||||
.pool
|
.pool
|
||||||
.get()
|
.get()
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||||
|
|
||||||
let query = projects.filter(id.eq(msg.project_id));
|
let query = projects.find(msg.project_id);
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
"{}",
|
"{}",
|
||||||
|
Loading…
Reference in New Issue
Block a user