Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Admin panel #364

Draft
wants to merge 2 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions src/components/EditableField/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import './index.scss'
import React, {Fragment} from 'react';
import PropTypes from 'prop-types'
import {HelTextField} from '../HelFormFields';
import {FormattedMessage} from 'react-intl';
import {Button} from 'reactstrap';
import {updateOrganization} from '../../utils/user';

export default class EditableField extends React.Component {
constructor(props) {
super(props);
this.state = {
editing: false,
fieldValue: null,
}
}

onEdit = () => {
this.setState({editing: !this.state.editing});
}

onSubmit = () => {
const {organization, fetchOrganizations} = this.props;
const data = {
name: this.state.fieldValue,
}
updateOrganization(organization.id, data);
fetchOrganizations();
this.setState({editing: !this.state.editing});
}

handleChange = (event) => {
this.setState({fieldValue: event.target.value});
}

render() {
const initialValue = this.props.organization.name;
return (
<div className="editablefield-wrapper">
{this.state.editing ?
<div className="editablefield-edit">
<h4><FormattedMessage id={'organization-name'}>{txt => txt}</FormattedMessage></h4>
<div className="editablefield-wrapper">
<HelTextField defaultValue={initialValue} onChange={(event) => this.handleChange(event)} />
<Button variant="contained" onClick={() => this.onSubmit()}>
<FormattedMessage id='save'>{txt =>txt}</FormattedMessage>
</Button>
<Button variant="contained" onClick={() => this.onEdit()}>
<FormattedMessage id='cancel'>{txt =>txt}</FormattedMessage>
</Button>
</div>
</div>
:
<Fragment>
<h1>{this.state.fieldValue ? this.state.fieldValue : initialValue }</h1>
<span className='glyphicon glyphicon-pencil' onClick={() => this.onEdit()} />
</Fragment>
}
</div>
);
}
}

EditableField.propTypes = {
organization: PropTypes.object,
fetchOrganizations: PropTypes.func,
}
22 changes: 22 additions & 0 deletions src/components/EditableField/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.editablefield-wrapper {
display: flex;
flex-direction: row;
align-items: center;
gap: 16px;
span {
display: flex;
flex-direction: row;
align-items: center;
color: #0262AE;
width: 24px;
height: 24px;
cursor: pointer;
}
.event-input {
display: flex;
}
.editablefield-edit {
display: flex;
flex-direction: column;
}
}
22 changes: 22 additions & 0 deletions src/components/Header/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ class HeaderBar extends React.Component {
});
};

userIsAdmin = () => {
return get(this.props.user, 'userType') === USER_TYPE.ADMIN;
}

handleLogoutClick = () => {
// clear user data in redux store
this.props.clearUserData();
Expand Down Expand Up @@ -205,6 +209,24 @@ class HeaderBar extends React.Component {
<FormattedMessage id='more-info' />
</NavLink>
</NavItem>



{this.userIsAdmin() &&
<NavItem className="nav-item-pull-right">
<NavLink
strict={this.isActivePath('/admin')}
className='nav-link nav-item-button'
to='/admin'
onClick={() => this.handleOnClick('/admin')}>
<span aria-hidden className='glyphicon glyphicon-cog' />
<FormattedMessage id='admin-panel' />
</NavLink>
</NavItem>
}



</ul>
</Collapse>
</Navbar>
Expand Down
7 changes: 7 additions & 0 deletions src/components/Header/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,13 @@ $iconMarginLeft: 24px;
outline: solid 2px black;
}
}
.nav-item-button {
border: 1px solid black;
box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.25);
}
}
.nav-item-pull-right {
margin-left: auto;
}
&__list {
.moderator {
Expand Down
127 changes: 127 additions & 0 deletions src/components/UserPermissionTable/UserPermissionRow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import './index.scss'
import React from 'react';
import PropTypes from 'prop-types'
import {updateOrganization} from '../../utils/user';


export default class UserPermissionRow extends React.Component {
constructor(props) {
super(props);
this.state = {
editing: false,
userIsAdminUserCheckboxValue: this.userIsAdminUser(),
userIsRegularUserCheckboxValue: this.userIsRegularUser(),
}
}

toggleEdit = () => {
this.setState({editing: !this.state.editing});
}

handleDeleteUser = () => {
const organizationData = this.props.organization;
const newAdminUsers = organizationData.admin_users.filter(adminUser => adminUser.uuid != this.props.user.uuid);
const newRegularUsers = organizationData.regular_users.filter(regularUser => regularUser.uuid != this.props.user.uuid);
organizationData.admin_users = newAdminUsers;
organizationData.regular_users = newRegularUsers;
const organizationDataCopy = Object.assign({}, organizationData);
organizationDataCopy.admin_users = organizationDataCopy.admin_users.map((user) => user.pk);
organizationDataCopy.regular_users = organizationDataCopy.regular_users.map((user) => user.pk);
updateOrganization(organizationDataCopy.id, organizationDataCopy);
this.props.setUsers();
}

handleSubmit = (e) => {
const organizationData = this.props.organization;
if(this.userIsAdminUser() && !this.state.userIsAdminUserCheckboxValue) {
const newAdminUsers = organizationData.admin_users.filter(adminUser => adminUser.uuid != this.props.user.uuid);
organizationData.admin_users = newAdminUsers;
}
else if(!this.userIsAdminUser() && this.state.userIsAdminUserCheckboxValue) {
organizationData.admin_users.push(this.props.user);
}

if(this.userIsRegularUser() && !this.state.userIsRegularUserCheckboxValue) {
const newRegularUsers = organizationData.regular_users.filter(regularUser => regularUser.uuid != this.props.user.uuid);
organizationData.regular_users = newRegularUsers;
}
else if(!this.userIsRegularUser() && this.state.userIsRegularUserCheckboxValue) {
organizationData.regular_users = organizationData.regular_users.map((user) => user.pk);
organizationData.regular_users.push(this.props.user);
}
const organizationDataCopy = Object.assign({}, organizationData);
organizationDataCopy.admin_users = organizationDataCopy.admin_users.map((user) => user.pk);
organizationDataCopy.regular_users = organizationDataCopy.regular_users.map((user) => user.pk);
updateOrganization(organizationDataCopy.id, organizationDataCopy);

this.setState({editing: false});
this.props.setUsers();
}

userIsAdminUser = () => {
return this.props.user.userGroups.includes('admin');
}

userIsRegularUser = () => {
return this.props.user.userGroups.includes('regular');
}

getUserPermissionCheckboxes = () => {
return (
<div className="permission-checkboxes">
<input type="checkbox" id="userIsAdminUserCheckbox" defaultChecked={this.userIsAdminUser()} onChange={e => this.setState({userIsAdminUserCheckboxValue: e.target.checked})}/><label htmlFor="userIsAdminUserCheckbox">admin</label>
<input type="checkbox" id="userIsRegularUserCheckbox" defaultChecked={this.userIsRegularUser()} onChange={e => this.setState({userIsRegularUserCheckboxValue: e.target.checked})} /><label htmlFor="userIsRegularUserCheckbox">regular</label>
</div>
);
}

render() {
return (
<tr>
<td>{this.props.user.first_name} {this.props.user.last_name}</td>
{this.state.editing ?
<td>{this.getUserPermissionCheckboxes()}</td>
:
<td>{this.props.user.userGroups}</td>
}
<td>
{this.state.editing ?
<>
<span onClick={this.handleSubmit} className="submit-permission-changes-button">
<svg width="22" height="18" viewBox="0 0 18 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 7L7 13L17 1" stroke="#009F1A" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</span>
<span onClick={this.toggleEdit} className="cancel-permission-changes-button">
<svg width="16" height="16" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.192 0.343994L5.94897 4.58599L1.70697 0.343994L0.292969 1.75799L4.53497 5.99999L0.292969 10.242L1.70697 11.656L5.94897 7.41399L10.192 11.656L11.606 10.242L7.36397 5.99999L11.606 1.75799L10.192 0.343994Z" fill="#DC3545"/>
</svg>
</span>
</>
:
<span onClick={this.toggleEdit} className="edit-permission-button">
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 13.6646V16.9975H3.33287L13.1626 7.16775L9.82975 3.83488L0 13.6646ZM15.74 4.59033C16.0867 4.24371 16.0867 3.68379 15.74 3.33717L13.6603 1.25746C13.3137 0.910843 12.7538 0.910843 12.4072 1.25746L10.7807 2.8839L14.1136 6.21677L15.74 4.59033Z" fill="#0262AE"/>
</svg>
</span>
}
</td>
<td>
<span className="delete-user-button" onClick={this.handleDeleteUser}>
<svg width="14" height="17" viewBox="0 0 14 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.93691 2.5H5.06191C4.98619 2.20438 4.97902 1.89536 5.04094 1.59654C5.10287 1.29772 5.23225 1.01701 5.41922 0.77582C5.60619 0.534634 5.84579 0.339358 6.11974 0.204901C6.39369 0.0704435 6.69474 0.000359624 6.99991 0C7.30499 0.000511782 7.60592 0.0707073 7.87975 0.20523C8.15357 0.339753 8.39305 0.535048 8.5799 0.776214C8.76676 1.01738 8.89605 1.29804 8.95792 1.59679C9.0198 1.89553 9.01261 2.20446 8.93691 2.5Z" fill="#0262AE"/>
<path d="M1.5 3.5C1.23478 3.5 0.98043 3.39464 0.792893 3.20711C0.605357 3.01957 0.5 2.76522 0.5 2.5C0.5 2.23478 0.605357 1.98043 0.792893 1.79289C0.98043 1.60536 1.23478 1.5 1.5 1.5H12.5C12.7652 1.5 13.0196 1.60536 13.2071 1.79289C13.3946 1.98043 13.5 2.23478 13.5 2.5C13.5 2.76522 13.3946 3.01957 13.2071 3.20711C13.0196 3.39464 12.7652 3.5 12.5 3.5H1.5Z" fill="#0262AE"/>
<path fillRule="evenodd" clipRule="evenodd" d="M11.5 16.5C11.7652 16.5 12.0196 16.3946 12.2071 16.2071C12.3946 16.0196 12.5 15.7652 12.5 15.5V5C12.5 4.73478 12.3946 4.48043 12.2071 4.29289C12.0196 4.10536 11.7652 4 11.5 4H2.5C2.23478 4 1.98043 4.10536 1.79289 4.29289C1.60536 4.48043 1.5 4.73478 1.5 5V15.5C1.5 15.7652 1.60536 16.0196 1.79289 16.2071C1.98043 16.3946 2.23478 16.5 2.5 16.5H11.5ZM9.5 6.5C9.5 6.36739 9.55268 6.24021 9.64645 6.14645C9.74021 6.05268 9.86739 6 10 6C10.1326 6 10.2598 6.05268 10.3536 6.14645C10.4473 6.24021 10.5 6.36739 10.5 6.5V13.5C10.5 13.6326 10.4473 13.7598 10.3536 13.8536C10.2598 13.9473 10.1326 14 10 14C9.86739 14 9.74021 13.9473 9.64645 13.8536C9.55268 13.7598 9.5 13.6326 9.5 13.5V6.5ZM7 6C6.86739 6 6.74021 6.05268 6.64645 6.14645C6.55268 6.24021 6.5 6.36739 6.5 6.5V13.5C6.5 13.6326 6.55268 13.7598 6.64645 13.8536C6.74021 13.9473 6.86739 14 7 14C7.13261 14 7.25979 13.9473 7.35355 13.8536C7.44732 13.7598 7.5 13.6326 7.5 13.5V6.5C7.5 6.36739 7.44732 6.24021 7.35355 6.14645C7.25979 6.05268 7.13261 6 7 6ZM3.5 6.5C3.5 6.36739 3.55268 6.24021 3.64645 6.14645C3.74021 6.05268 3.86739 6 4 6C4.13261 6 4.25979 6.05268 4.35355 6.14645C4.44732 6.24021 4.5 6.36739 4.5 6.5V13.5C4.5 13.6326 4.44732 13.7598 4.35355 13.8536C4.25979 13.9473 4.13261 14 4 14C3.86739 14 3.74021 13.9473 3.64645 13.8536C3.55268 13.7598 3.5 13.6326 3.5 13.5V6.5Z" fill="#0262AE"/>
</svg>
</span>
</td>
</tr>
);
}
}

UserPermissionRow.propTypes = {
user: PropTypes.object,
organization: PropTypes.object,
setUsers: PropTypes.func,
}
84 changes: 84 additions & 0 deletions src/components/UserPermissionTable/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import './index.scss'
import React from 'react';
import PropTypes from 'prop-types'
import {Table} from 'react-bootstrap';
import UserPermissionRow from './UserPermissionRow';
import {FormattedMessage} from 'react-intl';


export default class UserPermissionTable extends React.Component {
constructor(props) {
super(props);
this.state = {
users: this.getUsers(),
}
}

getUsers() {
const allUsers = this.props.organization.admin_users.concat(this.props.organization.regular_users);
const adminIds = this.props.organization.admin_users.map(user => user.uuid);
const regularIds = this.props.organization.regular_users.map(user => user.uuid);
const users = [];
const addedUserIds = [];

for (let user of allUsers) {
if(addedUserIds.includes(user.uuid)) {
continue;
}
if(adminIds.includes(user.uuid) && regularIds.includes(user.uuid)) {
user.userGroups = 'admin, regular';
}
else if(!adminIds.includes(user.uuid) && regularIds.includes(user.uuid)) {
user.userGroups = 'regular';
}
else if(adminIds.includes(user.uuid) && !regularIds.includes(user.uuid)) {
user.userGroups = 'admin';
}
users.push(user);
addedUserIds.push(user.uuid);
}
return users;
}

setUsers() {
this.setState({
users: this.getUsers(),
})
}

getUserRows() {
const users = this.state.users;
return users.map((user) =>
<UserPermissionRow
key={user.id}
user={user}
organization={this.props.organization}
setUsers={() => this.setUsers()}
/>
);
}

render() {
return (
<div>
<Table className="user-permission-table" ref={this.foo}>
<thead>
<tr>
<th><FormattedMessage id='name'>{txt =>txt}</FormattedMessage></th>
<th><FormattedMessage id='permissions'>{txt =>txt}</FormattedMessage></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{this.getUserRows()}
</tbody>
</Table>
</div>
);
}
}

UserPermissionTable.propTypes = {
organization: PropTypes.object,
}
20 changes: 20 additions & 0 deletions src/components/UserPermissionTable/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.user-permission-table {
margin-top: 32px;
}

#userIsAdminUserCheckbox,
#userIsRegularUserCheckbox,
.permission-checkboxes label {
margin-right: 8px;
}

.submit-permission-changes-button {
margin-right: 16px;
}

.submit-permission-changes-button,
.cancel-permission-changes-button,
.edit-permission-button,
.delete-user-button {
cursor: pointer;
}
7 changes: 6 additions & 1 deletion src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,7 @@
"edit-courses": "Edit the course",
"search-courses": "Search for courses",
"more-info": "More info",
"admin-panel": "Organization management",
"courses-management": "Course management",
"edit-image": "Edit image",
"The name must be defined.": "The filename must be defined",
Expand Down Expand Up @@ -700,5 +701,9 @@
"select-reader": "{count} {count plural, one {result} other {results}} available",

"editor-show-broader-concepts": "Show broader concepts for keyword '{value}'.",
"editor-show-narrower-concepts": "Show narrower concepts for keyword '{value}'."
"editor-show-narrower-concepts": "Show narrower concepts for keyword '{value}'.",

"organization-name": "Organization name",
"permissions": "Permissions",
"name": "Name"
}
Loading