From 91d2d8a9352457a082c4f8504f0988d7ec031941 Mon Sep 17 00:00:00 2001 From: zmagod Date: Thu, 31 Aug 2017 15:56:55 +0200 Subject: [PATCH] adds team details view --- .../client_api/users/user_teams_controller.rb | 61 ++------- app/javascript/packs/locales/messages.js | 35 ++++-- .../team/SettingsTeamPageContainer.jsx | 106 +++++++++++++++- .../team/components/TeamsMembers.jsx | 118 ++++++++++++++++++ .../teams/components/TeamsDataTable.jsx | 4 +- app/javascript/packs/styles/main.scss | 8 ++ app/services/client_api/uset_team_service.rb | 62 +++++++++ .../client_api/teams/details.json.jbuilder | 16 ++- config/routes.rb | 1 + package.json | 8 +- yarn.lock | 20 ++- 11 files changed, 359 insertions(+), 80 deletions(-) create mode 100644 app/javascript/packs/src/settings/components/team/components/TeamsMembers.jsx create mode 100644 app/services/client_api/uset_team_service.rb diff --git a/app/controllers/client_api/users/user_teams_controller.rb b/app/controllers/client_api/users/user_teams_controller.rb index de1a39f8f..b2c462a43 100644 --- a/app/controllers/client_api/users/user_teams_controller.rb +++ b/app/controllers/client_api/users/user_teams_controller.rb @@ -1,50 +1,31 @@ module ClientApi module Users class UserTeamsController < ApplicationController - include NotificationsHelper - include InputSanitizeHelper include ClientApi::Users::UserTeamsHelper before_action :find_user_team, only: :leave_team def leave_team - if user_cant_leave? - unsuccess_response - else - begin - assign_new_team_owner - generate_new_notification - success_response - rescue - unsuccess_response - end - end + user_team_service = ClientApi::UserTeamService.new(user: current_user) + user_team_service.destroy_user_team_and_assign_new_team_owner! + success_response(user_team_service.teams_data) + rescue + unsuccess_response + end + + def update_role + end private - def find_user_team - @team = Team.find_by_id(params[:team]) - @user_team = UserTeam.where(team: @team, user: current_user).first - end - - def user_cant_leave? - return unless @user_team && @team - @user_team.admin? && - @team.user_teams.where(role: 2).count <= 1 - end - - def success_response + def success_response(locals) respond_to do |format| # return a list of teams format.json do render template: '/client_api/teams/index', status: :ok, - locals: { - teams: current_user.teams_data, - flash_message: t('client_api.user_teams.leave_flash', - team: @team.name) - } + locals: locals end end end @@ -60,27 +41,7 @@ module ClientApi end end - def assign_new_team_owner - new_owner = @team.user_teams - .where(role: 2) - .where.not(id: @user_team.id) - .first.user - new_owner ||= current_user - reset_user_current_team(@user_team) - @user_team.destroy(new_owner) - end - def reset_user_current_team(user_team) - ids = user_team.user.teams_ids - ids -= [user_team.team.id] - user_team.user.current_team_id = ids.first - user_team.user.save - end - - def generate_new_notification - generate_notification(@user_team.user, @user_team.user, @user_team.team, - false, false) - end end end end diff --git a/app/javascript/packs/locales/messages.js b/app/javascript/packs/locales/messages.js index 7932e3533..adab6e74a 100644 --- a/app/javascript/packs/locales/messages.js +++ b/app/javascript/packs/locales/messages.js @@ -22,15 +22,6 @@ export default { in_team: "You are member of {num} team", in_teams: "You are member of {num} team", leave_team: "Leave team", - leave_team_modal: { - title: "Leave team {teamName}", - subtitle: "Are you sure you wish to leave team My projects? This action is irreversible.", - warnings: "Leaving team has following consequences:", - warning_message_one: "you will lose access to all content belonging to the team (including projects, tasks, protocols and activities);", - warning_message_two: "all projects in the team where you were the sole Owner will receive a new owner from the team administrators;", - warning_message_three: "all repository protocols in the team belonging to you will be reassigned onto a new owner from team administrators.", - leave_team: "Leave" - }, account: "Account", team: "Team", avatar: "Avatar", @@ -66,7 +57,31 @@ export default { show_in_scinote: "Show in sciNote", notify_me_via_email: "Notify me via email", no: "No", - yes: "Yes" + yes: "Yes", + leave_team_modal: { + title: "Leave team {teamName}", + subtitle: "Are you sure you wish to leave team My projects? This action is irreversible.", + warnings: "Leaving team has following consequences:", + warning_message_one: "you will lose access to all content belonging to the team (including projects, tasks, protocols and activities);", + warning_message_two: "all projects in the team where you were the sole Owner will receive a new owner from the team administrators;", + warning_message_three: "all repository protocols in the team belonging to you will be reassigned onto a new owner from team administrators.", + leave_team: "Leave" + }, + single_team: { + created_on: "Created on: {created_at}", + created_by: "Created by: {created_by}", + space_usage: "Space usage: {space_usage}", + no_description: "No description", + members_panel_title: "Team members", + add_members: "Add team members", + actions: { + user_role: "User role", + guest: "Guest", + normal_user: "Normal user", + administrator: "Administrator", + remove_user: "Remove" + } + } }, activities: { modal_title: "Activities", diff --git a/app/javascript/packs/src/settings/components/team/SettingsTeamPageContainer.jsx b/app/javascript/packs/src/settings/components/team/SettingsTeamPageContainer.jsx index 22ad41961..2e4979620 100644 --- a/app/javascript/packs/src/settings/components/team/SettingsTeamPageContainer.jsx +++ b/app/javascript/packs/src/settings/components/team/SettingsTeamPageContainer.jsx @@ -1,7 +1,10 @@ import React, { Component } from "react"; -import PropTypes, { number, string, bool } from "prop-types"; +import ReactRouterPropTypes from "react-router-prop-types"; import styled from "styled-components"; -import { FormattedMessage } from "react-intl"; +import { Row, Col, Glyphicon, Well } from "react-bootstrap"; +import { FormattedHTMLMessage, FormattedMessage } from "react-intl"; +import moment from "moment"; +import prettysize from "prettysize"; import axios from "../../../../app/axios"; import { TEAM_DETAILS_PATH } from "../../../../app/routes"; @@ -10,6 +13,8 @@ import { COLOR_CONCRETE } from "../../../../app/constants/colors"; +import TeamsMembers from "./components/TeamsMembers"; + const Wrapper = styled.div` background: white; box-sizing: border-box; @@ -24,6 +29,21 @@ const TabTitle = styled.div` padding: 15px; `; +const BadgeWrapper = styled.div` + font-size: 1.4em; + float: left; + padding: 6px 10px; + background-color: #37a0d9; + color: #fff; +`; + +const StyledWell = styled.div` + &:hover { + text-decoration: underline; + cursor: pointer; + } +`; + class SettingsTeamPageContainer extends Component { constructor(props) { super(props); @@ -31,24 +51,102 @@ class SettingsTeamPageContainer extends Component { team: {}, users: [] }; + this.updateDescription = this.updateDescription.bind(this); + this.updateRole = this.updateRole.bind(this) } componentDidMount() { - const path = TEAM_DETAILS_PATH.replace(":team_id", this.props.params.id); + const { id } = this.props.match.params; + const path = TEAM_DETAILS_PATH.replace(":team_id", id); axios.get(path).then(response => { - + const { team, users } = response.data.team_details; + this.setState({ team, users }); }); } + updateDescription() { + console.log("banana"); + } + + updateRole(userId) { + + } + + renderDescription() { + if (this.state.team.description) { + return this.state.team.description; + } + return ( + + ); + } + render() { return ( + {` / ${this.state.team.name}`} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {this.renderDescription()} + + + + ); } } +SettingsTeamPageContainer.PropTypes = { + match: ReactRouterPropTypes.match.isRequired +}; + export default SettingsTeamPageContainer; diff --git a/app/javascript/packs/src/settings/components/team/components/TeamsMembers.jsx b/app/javascript/packs/src/settings/components/team/components/TeamsMembers.jsx new file mode 100644 index 000000000..d98332085 --- /dev/null +++ b/app/javascript/packs/src/settings/components/team/components/TeamsMembers.jsx @@ -0,0 +1,118 @@ +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import { + Panel, + Button, + Glyphicon, + DropdownButton, + MenuItem +} from "react-bootstrap"; +import { FormattedMessage } from "react-intl"; +import DataTable from "../../../../../shared/data_table"; + +class TeamsMembers extends Component { + constructor(params) { + super(params); + this.memberAction = this.memberAction.bind(this); + } + + currentRole(memberRole, role) { + return memberRole === role ? : " "; + } + + + memberAction(data, row) { + return ( + + + + + + {this.currentRole(data.current_role, "Guest")} + + + + {this.currentRole(data.current_role, "Normal user")} + + + + {this.currentRole(data.current_role, "Administrator")} + + + + + + + + ); + } + + render() { + const columns = [ + { + id: 1, + name: "Name", + isKey: false, + textId: "name", + position: 0, + dataSort: true + }, + { + id: 2, + name: "Email", + isKey: true, + textId: "email", + position: 1, + dataSort: true + }, + { + id: 3, + name: "Role", + isKey: false, + textId: "role", + position: 2, + dataSort: true + }, + { + id: 4, + name: "Joined on", + isKey: false, + textId: "created_at", + position: 3 + }, + { + id: 5, + name: "Status", + isKey: false, + textId: "status", + position: 3 + }, + { + id: 6, + name: "Actions", + isKey: false, + textId: "actions", + columnClassName: "react-bootstrap-table-dropdown-fix", + dataFormat: this.memberAction, + position: 3 + } + ]; + + return ( + + } + > + + + + + ); + } +} + +export default TeamsMembers; diff --git a/app/javascript/packs/src/settings/components/teams/components/TeamsDataTable.jsx b/app/javascript/packs/src/settings/components/teams/components/TeamsDataTable.jsx index 6b20c459b..318db408f 100644 --- a/app/javascript/packs/src/settings/components/teams/components/TeamsDataTable.jsx +++ b/app/javascript/packs/src/settings/components/teams/components/TeamsDataTable.jsx @@ -39,9 +39,9 @@ class TeamsDataTable extends Component { ); } - linkToTeam(name, col) { + linkToTeam(name, row) { return ( - + {name} ); diff --git a/app/javascript/packs/styles/main.scss b/app/javascript/packs/styles/main.scss index 25af11341..d24494f08 100644 --- a/app/javascript/packs/styles/main.scss +++ b/app/javascript/packs/styles/main.scss @@ -21,3 +21,11 @@ body { background-color: $primary-hover-color; } } + +// // fixes issue with dropdown in datatable +.react-bootstrap-table-dropdown-fix { + overflow: inherit !important; + & .open > .dropdown-menu { + position: relative !important; + } +} diff --git a/app/services/client_api/uset_team_service.rb b/app/services/client_api/uset_team_service.rb new file mode 100644 index 000000000..3c80c0dc8 --- /dev/null +++ b/app/services/client_api/uset_team_service.rb @@ -0,0 +1,62 @@ +module ClientApi + class UserTeamService + include NotificationsHelper + include InputSanitizeHelper + + def initialize(arg) + parsed_args = validate_params(args) + @team = Team.find_by_id(parsed_args.fetch(:team_id)) + @user = parsed_args.fetch(:user) + @user_team = UserTeam.find_by_id(parsed_args.fetch(:user_team_id)) + raise ClientApi::CustomUserTeamError unless @user_team.team == @team + end + + def destroy_user_team_and_assign_new_team_owner! + raise ClientApi::CustomUserTeamError unless user_cant_leave? + new_owner = @team.user_teams + .where(role: 2) + .where.not(id: @user_team.id) + .first.user + new_owner ||= @user + reset_user_current_team(@user_team) + @user_team.destroy(new_owner) + generate_new_notification + end + + def teams_data + { + teams: @user.teams_data, + flash_message: t('client_api.user_teams.leave_flash', team: @team.name) + } + end + private + + def reset_user_current_team(user_team) + ids = user_team.user.teams_ids + ids -= [user_team.team.id] + user_team.user.current_team_id = ids.first + user_team.user.save + end + + def user_cant_leave? + @user_team.admin? && @team.user_teams.where(role: 2).count <= 1 + end + + def generate_new_notification + user = @user_team.user + generate_notification(user, + user, + @user_team.team, + false, + false) + end + + def validate_params(args) + team_id = args.fetch(:team_id) { raise ClientApi::CustomUserTeamError } + user_team_id = args.fetch(:user_team_id) { raise ClientApi::CustomUserTeamError } + user = args.fetch(:user) { raise ClientApi::CustomUserTeamError } + {user: user, user_team_id: user_team_id, team_id: team_id } + end + end + CustomUserTeamError = Class.new(StandardError) +end diff --git a/app/views/client_api/teams/details.json.jbuilder b/app/views/client_api/teams/details.json.jbuilder index 967c9cbe1..858dd6a01 100644 --- a/app/views/client_api/teams/details.json.jbuilder +++ b/app/views/client_api/teams/details.json.jbuilder @@ -1,12 +1,16 @@ json.team_details do - json.id team.id - json.created_at team.created_at - json.created_by "#{team.created_by.full_name} (#{team.created_by.email})" - json.space_taken team.space_taken - json.description team.description + json.team do + json.id team.id + json.name team.name + json.created_at team.created_at + json.created_by "#{team.created_by.full_name} (#{team.created_by.email})" + json.space_taken team.space_taken + json.description team.description + end json.users team_users do |team_user| json.id team_user.user.id - json.email team_user.user.full_name + json.name team_user.user.full_name + json.email team_user.user.email json.role team_user.role_str json.created_at I18n.l(team_user.created_at, format: :full_date) json.status team_user.user.active_status_str diff --git a/config/routes.rb b/config/routes.rb index 09de8ed86..63a28779f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -29,6 +29,7 @@ Rails.application.routes.draw do namespace :users do delete '/leave_team', to: 'user_teams#leave_team' + put '/update_role', to: 'user_teams#update_role' end end diff --git a/package.json b/package.json index 10fcfa26a..ad2c953f7 100644 --- a/package.json +++ b/package.json @@ -59,11 +59,14 @@ "postcss-loader": "^2.0.6", "postcss-smart-import": "^0.7.5", "precss": "^2.0.0", + "prettysize": "^0.1.0", "prop-types": "^15.5.10", "rails-erb-loader": "^5.0.2", "react": "^15.6.1", "react-bootstrap": "^0.31.1", + "react-bootstrap-table": "^4.0.0", "react-bootstrap-timezone-picker": "^1.0.11", + "react-data-grid": "^2.0.2", "react-dom": "^15.6.1", "react-intl": "^2.3.0", "react-intl-redux": "^0.6.0", @@ -71,6 +74,7 @@ "react-redux": "^5.0.5", "react-router-bootstrap": "^0.24.2", "react-router-dom": "^4.1.2", + "react-router-prop-types": "^0.0.1", "react-timezone": "^0.2.0", "redux": "^3.7.2", "redux-thunk": "^2.2.0", @@ -80,8 +84,6 @@ "styled-components": "^2.1.1", "webpack": "^3.2.0", "webpack-manifest-plugin": "^1.1.2", - "webpack-merge": "^4.1.0", - "react-bootstrap-table": "^4.0.0", - "react-data-grid": "^2.0.2" + "webpack-merge": "^4.1.0" } } diff --git a/yarn.lock b/yarn.lock index ba14d0a0e..9e8f9b798 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4516,6 +4516,10 @@ prettier@^1.5.3: version "1.5.3" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.5.3.tgz#59dadc683345ec6b88f88b94ed4ae7e1da394bfe" +prettysize@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/prettysize/-/prettysize-0.1.0.tgz#38ee534e2d298bc945fb7243203dd873cefc9679" + private@^0.1.6, private@^0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/private/-/private-0.1.7.tgz#68ce5e8a1ef0a23bb570cc28537b5332aba63ef1" @@ -4550,7 +4554,7 @@ prop-types-extra@^1.0.1: dependencies: warning "^3.0.0" -prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.7, prop-types@^15.5.8: +prop-types@15.5.10, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.7, prop-types@^15.5.8: version "15.5.10" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154" dependencies: @@ -4723,10 +4727,6 @@ react-intl@^2.3.0: intl-relativeformat "^1.3.0" invariant "^2.1.1" -react-moment@^0.6.4: - version "0.6.4" - resolved "https://registry.yarnpkg.com/react-moment/-/react-moment-0.6.4.tgz#5e531d47ad7b0bff6f6b7175093e98659f5e667b" - react-modal@^1.4.0: version "1.9.7" resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-1.9.7.tgz#07ef56790b953e3b98ef1e2989e347983c72871d" @@ -4738,6 +4738,10 @@ react-modal@^1.4.0: prop-types "^15.5.7" react-dom-factories "^1.0.0" +react-moment@^0.6.4: + version "0.6.4" + resolved "https://registry.yarnpkg.com/react-moment/-/react-moment-0.6.4.tgz#5e531d47ad7b0bff6f6b7175093e98659f5e667b" + react-overlays@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-0.7.0.tgz#531898ff566c7e5c7226ead2863b8cf9fbb5a981" @@ -4781,6 +4785,12 @@ react-router-dom@^4.1.2: prop-types "^15.5.4" react-router "^4.2.0" +react-router-prop-types@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/react-router-prop-types/-/react-router-prop-types-0.0.1.tgz#fd7fe4431f8ee104cdb250c738f410eb85847377" + dependencies: + prop-types "15.5.10" + react-router@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.2.0.tgz#61f7b3e3770daeb24062dae3eedef1b054155986"