diff --git a/public/app/listeners/editClassroom.js b/public/app/listeners/editClassroom.js index 6bca874e..da87f65e 100644 --- a/public/app/listeners/editClassroom.js +++ b/public/app/listeners/editClassroom.js @@ -1,9 +1,17 @@ const { EDIT_CLASSROOM_CHANNEL } = require('../config/channels'); const { ERROR_GENERAL } = require('../config/errors'); -const { CLASSROOMS_COLLECTION } = require('../db'); +const { + CLASSROOMS_COLLECTION, + SPACES_COLLECTION, + ACTIONS_COLLECTION, + APP_INSTANCE_RESOURCES_COLLECTION, +} = require('../db'); const logger = require('../logger'); -const editClassroom = (mainWindow, db) => async (event, { name, id }) => { +const editClassroom = (mainWindow, db) => async ( + event, + { name, id, deleteSelection } +) => { logger.debug('editing classroom'); try { @@ -15,6 +23,24 @@ const editClassroom = (mainWindow, db) => async (event, { name, id }) => { mainWindow.webContents.send(EDIT_CLASSROOM_CHANNEL, ERROR_GENERAL); } + // delete selected space and their resources + Object.entries(deleteSelection).forEach(([spaceId, selected]) => { + if (selected) { + classroom + .get(SPACES_COLLECTION) + .remove({ id: spaceId }) + .write(); + classroom + .get(ACTIONS_COLLECTION) + .remove({ spaceId }) + .write(); + classroom + .get(APP_INSTANCE_RESOURCES_COLLECTION) + .remove({ spaceId }) + .write(); + } + }); + // update data const now = new Date(); classroom.assign({ name, updatedAt: now }).write(); diff --git a/public/app/listeners/editUserInClassroom.js b/public/app/listeners/editUserInClassroom.js index b9315230..f7342b2e 100644 --- a/public/app/listeners/editUserInClassroom.js +++ b/public/app/listeners/editUserInClassroom.js @@ -1,20 +1,26 @@ const { EDIT_USER_IN_CLASSROOM_CHANNEL } = require('../config/channels'); const { ERROR_GENERAL } = require('../config/errors'); -const { CLASSROOMS_COLLECTION, USERS_COLLECTION } = require('../db'); +const { + CLASSROOMS_COLLECTION, + USERS_COLLECTION, + ACTIONS_COLLECTION, + APP_INSTANCE_RESOURCES_COLLECTION, +} = require('../db'); const logger = require('../logger'); +/** + * @param {Object} deleteSelection : object mapping space id to whether the space data should be deleted + */ const editUserInClassroom = (mainWindow, db) => async ( event, - { username, userId, classroomId } + { username, userId, classroomId, deleteSelection } ) => { logger.debug('editing user in classroom'); try { - const user = db - .get(CLASSROOMS_COLLECTION) - .find({ id: classroomId }) - .get(USERS_COLLECTION) - .find({ id: userId }); + const classroom = db.get(CLASSROOMS_COLLECTION).find({ id: classroomId }); + + const user = classroom.get(USERS_COLLECTION).find({ id: userId }); // check user exists const found = user.value(); @@ -25,9 +31,19 @@ const editUserInClassroom = (mainWindow, db) => async ( ); } + // delete space data related to user if selected + const actions = classroom.get(ACTIONS_COLLECTION); + const resources = classroom.get(APP_INSTANCE_RESOURCES_COLLECTION); + Object.entries(deleteSelection).forEach(([spaceId, selected]) => { + if (selected) { + actions.remove({ spaceId, user: userId }).write(); + resources.remove({ spaceId, user: userId }).write(); + } + }); + // update data const now = new Date(); - user.assign({ username, lastUpdatedAt: now }).write(); + user.assign({ username, updatedAt: now }).write(); mainWindow.webContents.send(EDIT_USER_IN_CLASSROOM_CHANNEL); } catch (err) { diff --git a/public/app/listeners/loadSpaceInClassroom.js b/public/app/listeners/loadSpaceInClassroom.js index e3d2f2ca..c009f52a 100644 --- a/public/app/listeners/loadSpaceInClassroom.js +++ b/public/app/listeners/loadSpaceInClassroom.js @@ -38,13 +38,35 @@ const loadSpaceInClassroom = (mainWindow, db) => async ( try { const classroom = db.get(CLASSROOMS_COLLECTION).find({ id: classroomId }); - // add user if doesn't exist - let user = classroom - .get(USERS_COLLECTION) - .find({ username }) - .value(); - if (!user) { - user = addUserInClassroomDatabase(db, { username, id: classroomId }); + // username should be defined if add resources or actions + if (isResourcesSelected || isActionsSelected) { + if (!username) { + logger.debug('username not specified'); + return mainWindow.webContents.send( + LOAD_SPACE_IN_CLASSROOM_CHANNEL, + ERROR_GENERAL + ); + } + } + + // add user + let user = null; + if (username) { + user = classroom + .get(USERS_COLLECTION) + .find({ username }) + .value(); + if (!user) { + try { + user = addUserInClassroomDatabase(db, { username, id: classroomId }); + } catch (err) { + logger.debug(err); + return mainWindow.webContents.send( + LOAD_SPACE_IN_CLASSROOM_CHANNEL, + err + ); + } + } } // todo: check teacher can write in classroom @@ -101,8 +123,6 @@ const loadSpaceInClassroom = (mainWindow, db) => async ( clean(extractPath); } - const { id: userId } = user; - // write resources to database if selected if (isResourcesSelected) { if (_.isEmpty(appInstanceResources)) { @@ -113,6 +133,7 @@ const loadSpaceInClassroom = (mainWindow, db) => async ( ); } + const { id: userId } = user; const savedResources = classroom.get(APP_INSTANCE_RESOURCES_COLLECTION); // remove previous corresponding resources @@ -138,6 +159,7 @@ const loadSpaceInClassroom = (mainWindow, db) => async ( ); } + const { id: userId } = user; const savedActions = classroom.get(ACTIONS_COLLECTION); // remove previous corresponding actions diff --git a/src/actions/classroom.js b/src/actions/classroom.js index 0107ae82..7661a50c 100644 --- a/src/actions/classroom.js +++ b/src/actions/classroom.js @@ -24,8 +24,8 @@ import { } from '../types'; import { ERROR_GENERAL, + ERROR_ACCESS_DENIED_CLASSROOM, ERROR_DUPLICATE_CLASSROOM_NAME, - ERROR_LOADING_MESSAGE, ERROR_INVALID_USERNAME, ERROR_DUPLICATE_USERNAME_IN_CLASSROOM, ERROR_NO_USER_TO_DELETE, @@ -67,6 +67,8 @@ import { ERROR_GETTING_SPACE_IN_CLASSROOM_MESSAGE, ERROR_INVALID_USERNAME_MESSAGE, SUCCESS_SPACE_LOADED_MESSAGE, + ERROR_LOADING_MESSAGE, + ERROR_ACCESS_DENIED_CLASSROOM_MESSAGE, } from '../config/messages'; import { createFlag } from './common'; import { createExtractFile, createClearLoadSpace } from './loadSpace'; @@ -88,10 +90,14 @@ export const getClassrooms = () => dispatch => { // create listener window.ipcRenderer.once(GET_CLASSROOMS_CHANNEL, (event, classrooms) => { // dispatch that the getter has succeeded - dispatch({ - type: GET_CLASSROOMS_SUCCEEDED, - payload: classrooms, - }); + if (classrooms === ERROR_ACCESS_DENIED_CLASSROOM) { + toastr.error(ERROR_MESSAGE_HEADER, ERROR_ACCESS_DENIED_CLASSROOM_MESSAGE); + } else { + dispatch({ + type: GET_CLASSROOMS_SUCCEEDED, + payload: classrooms, + }); + } dispatch(flagGettingClassrooms(false)); }); }; @@ -110,6 +116,12 @@ export const getClassroom = async payload => async dispatch => { } switch (response) { + case ERROR_ACCESS_DENIED_CLASSROOM: + toastr.error( + ERROR_MESSAGE_HEADER, + ERROR_ACCESS_DENIED_CLASSROOM_MESSAGE + ); + break; case ERROR_GENERAL: toastr.error(ERROR_MESSAGE_HEADER, ERROR_GETTING_CLASSROOM_MESSAGE); break; @@ -206,7 +218,8 @@ export const editClassroom = payload => dispatch => { if (response === ERROR_GENERAL) { toastr.error(ERROR_MESSAGE_HEADER, ERROR_EDITING_CLASSROOM_MESSAGE); } else { - // update saved classrooms in state + // update saved classrooms and current classroom in state + dispatch(getClassroom(payload)); dispatch(getClassrooms()); toastr.success(SUCCESS_MESSAGE_HEADER, SUCCESS_EDITING_CLASSROOM_MESSAGE); diff --git a/src/components/classrooms/AddClassroomButton.js b/src/components/classrooms/AddClassroomButton.js index 6f20bdd7..e5f816d7 100644 --- a/src/components/classrooms/AddClassroomButton.js +++ b/src/components/classrooms/AddClassroomButton.js @@ -6,7 +6,6 @@ import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import Button from '@material-ui/core/Button'; import { withTranslation } from 'react-i18next'; -import TextField from '@material-ui/core/TextField'; import Dialog from '@material-ui/core/Dialog'; import DialogActions from '@material-ui/core/DialogActions'; import DialogContent from '@material-ui/core/DialogContent'; @@ -14,10 +13,10 @@ import DialogTitle from '@material-ui/core/DialogTitle'; import { addClassroom } from '../../actions'; import { ADD_CLASSROOM_BUTTON_ID, - ADD_CLASSROOM_NAME_INPUT_ID, ADD_CLASSROOM_VALIDATE_BUTTON_ID, ADD_CLASSROOM_CANCEL_BUTTON_ID, } from '../../config/selectors'; +import ClassroomNameTextField from './ClassroomNameTextField'; const styles = theme => ({ fab: { @@ -41,18 +40,25 @@ class AddClassroomButton extends Component { state = (() => { const { t } = this.props; + const defaultName = t('New Classroom'); return { open: false, - name: t('New Classroom'), + name: defaultName, }; })(); + isClassroomNameValid = () => { + const { name } = this.state; + // todo: check for special characters + return name.trim().length; + }; + handleClickOpen = () => { this.setState({ open: true }); }; close = () => { - this.setState({ name: '', open: false }); + this.setState({ open: false }); }; handleCancel = () => { @@ -62,11 +68,14 @@ class AddClassroomButton extends Component { handleValidate = () => { const { name } = this.state; const { userId, dispatchAddClassroom } = this.props; - dispatchAddClassroom({ name, userId }); - this.close(); + if (this.isClassroomNameValid()) { + const trimmedName = name.trim(); + dispatchAddClassroom({ name: trimmedName, userId }); + this.close(); + } }; - handleChange = event => { + handleNameChange = event => { const { target } = event; this.setState({ name: target.value }); }; @@ -96,15 +105,9 @@ class AddClassroomButton extends Component { {t('Enter a name for your new classroom')} - @@ -119,6 +122,7 @@ class AddClassroomButton extends Component { onClick={this.handleValidate} color="primary" id={ADD_CLASSROOM_VALIDATE_BUTTON_ID} + disabled={!this.isClassroomNameValid()} > {t('Validate')} diff --git a/src/components/classrooms/ClassroomNameTextField.js b/src/components/classrooms/ClassroomNameTextField.js new file mode 100644 index 00000000..21b108ba --- /dev/null +++ b/src/components/classrooms/ClassroomNameTextField.js @@ -0,0 +1,43 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withTranslation } from 'react-i18next'; +import TextField from '@material-ui/core/TextField'; +import { CLASSROOM_NAME_INPUT_ID } from '../../config/selectors'; +import { isClassroomNameValid } from '../../utils/classroom'; + +const ClassroomNameTextField = ({ name, t, handleChange }) => { + const isValid = isClassroomNameValid(name); + let errorProps = {}; + if (!isValid) { + errorProps = { + ...errorProps, + helperText: t(`Classroom's name is not valid`), + error: true, + }; + } + + return ( + + ); +}; + +ClassroomNameTextField.propTypes = { + name: PropTypes.string.isRequired, + handleChange: PropTypes.func.isRequired, + t: PropTypes.func.isRequired, +}; + +const TranslatedComponent = withTranslation()(ClassroomNameTextField); + +export default TranslatedComponent; diff --git a/src/components/classrooms/ClassroomScreen.js b/src/components/classrooms/ClassroomScreen.js index 46a5eae1..60726b2c 100644 --- a/src/components/classrooms/ClassroomScreen.js +++ b/src/components/classrooms/ClassroomScreen.js @@ -3,7 +3,7 @@ import { connect } from 'react-redux'; import { withStyles } from '@material-ui/core'; import { withTranslation } from 'react-i18next'; import { withRouter } from 'react-router'; -import { Map } from 'immutable'; +import { Map, Set } from 'immutable'; import PropTypes from 'prop-types'; import Button from '@material-ui/core/Button'; import Toolbar from '@material-ui/core/Toolbar'; @@ -45,6 +45,7 @@ class ClassroomScreen extends Component { goBack: PropTypes.func.isRequired, }).isRequired, userId: PropTypes.string.isRequired, + classrooms: PropTypes.instanceOf(Set).isRequired, }; static defaultProps = { @@ -62,6 +63,22 @@ class ClassroomScreen extends Component { dispatchGetClassroom({ id, userId }); } + componentDidUpdate({ classrooms: prevClassrooms }) { + const { + dispatchGetClassroom, + classrooms, + match: { + params: { id }, + }, + userId, + } = this.props; + + // update current classroom if classrooms change + if (!prevClassrooms.equals(classrooms)) { + dispatchGetClassroom({ id, userId }); + } + } + renderBackButton = () => { const { t, @@ -117,6 +134,7 @@ class ClassroomScreen extends Component { } const mapStateToProps = ({ classroom, authentication }) => ({ + classrooms: classroom.getIn(['classrooms']), classroom: classroom.getIn(['current', 'classroom']), activity: Boolean(classroom.get('activity').size), userId: authentication.getIn(['user', 'id']), diff --git a/src/components/classrooms/Classrooms.js b/src/components/classrooms/Classrooms.js index 27b0c330..26a5eed9 100644 --- a/src/components/classrooms/Classrooms.js +++ b/src/components/classrooms/Classrooms.js @@ -16,10 +16,18 @@ import ClassroomGrid from './ClassroomGrid'; import Styles from '../../Styles'; import { CLASSROOMS_MAIN_ID } from '../../config/selectors'; +const styles = theme => ({ + ...Styles(theme), + wrapper: { + padding: theme.spacing(3), + }, +}); + export class Classrooms extends Component { static propTypes = { classes: PropTypes.shape({ root: PropTypes.string.isRequired, + wrapper: PropTypes.string.isRequired, }).isRequired, classrooms: PropTypes.instanceOf(Set), dispatchGetClassrooms: PropTypes.func.isRequired, @@ -59,8 +67,9 @@ export class Classrooms extends Component { return (
- - +
+ +
); @@ -82,7 +91,7 @@ const ConnectedComponent = connect( mapDispatchToProps )(Classrooms); -const StyledComponent = withStyles(Styles, { withTheme: true })( +const StyledComponent = withStyles(styles, { withTheme: true })( ConnectedComponent ); diff --git a/src/components/classrooms/EditClassroomButton.js b/src/components/classrooms/EditClassroomButton.js index 0e4e0e8e..d2f0f854 100644 --- a/src/components/classrooms/EditClassroomButton.js +++ b/src/components/classrooms/EditClassroomButton.js @@ -1,12 +1,17 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import EditIcon from '@material-ui/icons/Create'; +import { withStyles } from '@material-ui/core'; import { connect } from 'react-redux'; import Tooltip from '@material-ui/core/Tooltip'; import Button from '@material-ui/core/Button'; +import Grid from '@material-ui/core/Grid'; +import clsx from 'clsx'; +import CancelIcon from '@material-ui/icons/Cancel'; +import Typography from '@material-ui/core/Typography'; import IconButton from '@material-ui/core/IconButton'; import { withTranslation } from 'react-i18next'; -import TextField from '@material-ui/core/TextField'; +import DeleteIcon from '@material-ui/icons/Delete'; import Dialog from '@material-ui/core/Dialog'; import DialogActions from '@material-ui/core/DialogActions'; import DialogContent from '@material-ui/core/DialogContent'; @@ -14,39 +19,73 @@ import DialogTitle from '@material-ui/core/DialogTitle'; import { editClassroom } from '../../actions'; import { EDIT_CLASSROOM_BUTTON_CLASS, - EDIT_CLASSROOM_INPUT_ID, EDIT_CLASSROOM_VALIDATE_BUTTON_ID, EDIT_CLASSROOM_CANCEL_BUTTON_ID, + EDIT_CLASSROOM_DELETE_DATA_BUTTON_CLASS, } from '../../config/selectors'; +import ClassroomNameTextField from './ClassroomNameTextField'; +import { isClassroomNameValid } from '../../utils/classroom'; -class EditClassroomButton extends Component { - state = (() => { - const { - classroom: { name }, - } = this.props; +const styles = theme => ({ + deleted: { + color: 'lightgrey', + }, + dataTitle: { + fontSize: 'default', + fontWeight: 'bold', + marginTop: theme.spacing(2), + marginBottom: theme.spacing(1), + }, +}); - return { - open: false, - name, - }; - })(); +const buildDeleteSelection = spaces => { + return spaces.reduce((selection, { id: spaceId }) => { + return { ...selection, [spaceId]: false }; + }, {}); +}; +class EditClassroomButton extends Component { static propTypes = { + classes: PropTypes.shape({ + dataTitle: PropTypes.string.isRequired, + deleted: PropTypes.string.isRequired, + }).isRequired, dispatchEditClassroom: PropTypes.func.isRequired, classroom: PropTypes.shape({ id: PropTypes.string.isRequired, name: PropTypes.string.isRequired, + spaces: PropTypes.arrayOf(PropTypes.shape({})).isRequired, }).isRequired, t: PropTypes.func.isRequired, userId: PropTypes.string.isRequired, }; + state = (() => { + const { + classroom: { name, spaces }, + } = this.props; + + // build delete selection on contained spaces for given user + const deleteSelection = buildDeleteSelection(spaces); + + return { + open: false, + name, + deleteSelection, + }; + })(); + handleClickOpen = () => { this.setState({ open: true }); }; close = () => { - this.setState({ name: '', open: false }); + const { + classroom: { spaces, name }, + } = this.props; + // replace data with original state + const deleteSelection = buildDeleteSelection(spaces); + this.setState({ name, open: false, deleteSelection }); }; handleCancel = () => { @@ -54,14 +93,16 @@ class EditClassroomButton extends Component { }; handleValidate = () => { - const { name } = this.state; + const { name, deleteSelection } = this.state; const { dispatchEditClassroom, classroom: { id }, userId, } = this.props; - dispatchEditClassroom({ name, id, userId }); - this.close(); + if (isClassroomNameValid(name)) { + dispatchEditClassroom({ name, id, deleteSelection, userId }); + this.close(); + } }; handleChange = event => { @@ -69,6 +110,82 @@ class EditClassroomButton extends Component { this.setState({ name: target.value }); }; + changeDeleteSelection = (event, spaceId, value) => { + const { deleteSelection: prevDeleteSelection } = this.state; + const deleteSelection = { ...prevDeleteSelection, [spaceId]: value }; + this.setState({ deleteSelection }); + }; + + renderSpaces = () => { + const { + classroom: { spaces }, + classes, + t, + } = this.props; + const { deleteSelection } = this.state; + + return ( + <> + {t('Spaces')} + + {spaces.length ? ( + + {spaces.map(({ id: spaceId, name: spaceName }) => { + const isSelected = deleteSelection[spaceId]; + + const button = isSelected ? ( + + this.changeDeleteSelection(e, spaceId, false)} + > + + + + ) : ( + + this.changeDeleteSelection(e, spaceId, true)} + > + + + + ); + + return ( + + + {spaceName} + + + {button} + + + ); + })} + + ) : ( + {t('This classroom is empty')} + )} + + ); + }; + render() { const { t } = this.props; const { name, open } = this.state; @@ -91,19 +208,14 @@ class EditClassroomButton extends Component { aria-labelledby={DIALOG_TITLE_ID} > - {t('Edit Classroom Information')} + Edit Classroom information - + {this.renderSpaces()} @@ -126,6 +239,7 @@ class EditClassroomButton extends Component { ); } } + const mapStateToProps = ({ authentication }) => ({ userId: authentication.getIn(['user', 'id']), }); @@ -139,6 +253,10 @@ const ConnectedComponent = connect( mapDispatchToProps )(EditClassroomButton); -const TranslatedComponent = withTranslation()(ConnectedComponent); +const StyledComponent = withStyles(styles, { withTheme: true })( + ConnectedComponent +); + +const TranslatedComponent = withTranslation()(StyledComponent); export default TranslatedComponent; diff --git a/src/components/classrooms/EditUserInClassroomButton.js b/src/components/classrooms/EditUserInClassroomButton.js index 3f80091a..593f67e6 100644 --- a/src/components/classrooms/EditUserInClassroomButton.js +++ b/src/components/classrooms/EditUserInClassroomButton.js @@ -1,12 +1,21 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; +import clsx from 'clsx'; +import { Map } from 'immutable'; import { toastr } from 'react-redux-toastr'; import EditIcon from '@material-ui/icons/Create'; +import { withStyles } from '@material-ui/core'; import { connect } from 'react-redux'; +import DeleteIcon from '@material-ui/icons/Delete'; +import Grid from '@material-ui/core/Grid'; import Tooltip from '@material-ui/core/Tooltip'; import Button from '@material-ui/core/Button'; import IconButton from '@material-ui/core/IconButton'; import { withTranslation } from 'react-i18next'; +import Typography from '@material-ui/core/Typography'; +import ResourceIcon from '@material-ui/icons/AssignmentInd'; +import ActionIcon from '@material-ui/icons/Assessment'; +import CancelIcon from '@material-ui/icons/Cancel'; import TextField from '@material-ui/core/TextField'; import Dialog from '@material-ui/core/Dialog'; import DialogActions from '@material-ui/core/DialogActions'; @@ -18,36 +27,91 @@ import { EDIT_USER_IN_CLASSROOM_BUTTON_CLASS, EDIT_USER_IN_CLASSROOM_VALIDATE_BUTTON_ID, EDIT_USER_IN_CLASSROOM_USERNAME_INPUT_ID, + EDIT_USER_IN_CLASSROOM_DELETE_DATA_BUTTON_CLASS, } from '../../config/selectors'; import { ERROR_MESSAGE_HEADER, ERROR_INVALID_USERNAME_MESSAGE, } from '../../config/messages'; import { isUsernameValid } from '../../utils/user'; +import { + hasUserActionsForSpaceInClassroom, + hasUserResourcesForSpaceInClassroom, + hasUserDataInClassroom, +} from '../../utils/classroom'; -class EditUserInClassroomButton extends Component { - state = (() => { - const { - user: { username }, - } = this.props; - - return { - open: false, - username, - }; - })(); +const styles = theme => ({ + formControl: { + margin: theme.spacing(3), + }, + editSpaceRow: { + marginBottom: theme.spacing(3), + }, + deleted: { + color: 'lightgrey', + }, + dataTitle: { + fontSize: 'default', + fontWeight: 'bold', + marginTop: theme.spacing(2), + marginBottom: theme.spacing(1), + }, + dialog: { + minWidth: '40%', + }, +}); +class EditUserInClassroomButton extends Component { static propTypes = { + classes: PropTypes.shape({ + dataTitle: PropTypes.string.isRequired, + editSpaceRow: PropTypes.string.isRequired, + deleted: PropTypes.string.isRequired, + dialog: PropTypes.string.isRequired, + }).isRequired, user: PropTypes.shape({ id: PropTypes.string.isRequired, username: PropTypes.string.isRequired, }).isRequired, dispatchEditUserInClassroom: PropTypes.func.isRequired, - classroomId: PropTypes.string.isRequired, + classroom: PropTypes.instanceOf(Map).isRequired, t: PropTypes.func.isRequired, userId: PropTypes.string.isRequired, }; + state = (() => { + const { + user: { username, id: userId }, + classroom, + } = this.props; + + // build delete selection on contained spaces for given user + const deleteSelection = classroom + .get('spaces') + .reduce((selection, { id: spaceId }) => { + const hasResources = hasUserResourcesForSpaceInClassroom( + classroom, + spaceId, + userId + ); + const hasActions = hasUserActionsForSpaceInClassroom( + classroom, + spaceId, + userId + ); + if (hasResources || hasActions) { + return { ...selection, [spaceId]: false }; + } + return selection; + }, {}); + + return { + open: false, + username, + deleteSelection, + }; + })(); + handleClickOpen = () => { this.setState({ open: true }); }; @@ -66,11 +130,11 @@ class EditUserInClassroomButton extends Component { }; handleValidate = () => { - const { username } = this.state; + const { username, deleteSelection } = this.state; const { dispatchEditUserInClassroom, user: { id: userId }, - classroomId, + classroom, userId: teacherId, } = this.props; @@ -82,8 +146,9 @@ class EditUserInClassroomButton extends Component { dispatchEditUserInClassroom({ username: trimmedUsername, userId, - classroomId, teacherId, + classroomId: classroom.get('id'), + deleteSelection, }); this.close(); } @@ -96,10 +161,123 @@ class EditUserInClassroomButton extends Component { this.setState({ username: value }); }; + renderUsernameInput = usernameErrorProps => { + const { t } = this.props; + const { username } = this.state; + return ( + + ); + }; + + changeDeleteSelection = (event, spaceId, value) => { + const { deleteSelection: prevDeleteSelection } = this.state; + const deleteSelection = { ...prevDeleteSelection, [spaceId]: value }; + this.setState({ deleteSelection }); + }; + + renderUserData = () => { + const { + classroom, + classes, + t, + user: { id: userId }, + } = this.props; + const { deleteSelection } = this.state; + + return ( + <> + + {t('Actions and Resources')} + + + {hasUserDataInClassroom(classroom, userId) ? ( + + {classroom.get('spaces').map(({ id: spaceId, name: spaceName }) => { + const hasResources = hasUserResourcesForSpaceInClassroom( + classroom, + spaceId, + userId + ); + const hasActions = hasUserActionsForSpaceInClassroom( + classroom, + spaceId, + userId + ); + if (!hasActions && !hasResources) { + return null; + } + + const isSelected = deleteSelection[spaceId]; + + const button = isSelected ? ( + + this.changeDeleteSelection(e, spaceId, false)} + > + + + + ) : ( + + this.changeDeleteSelection(e, spaceId, true)} + > + + + + ); + + return ( + + + {spaceName} + {hasResources && } + {hasActions && } + + + {button} + + + ); + })} + + ) : ( + {t('This user has no content')} + )} + + ); + }; + render() { const { t, user: { username: name }, + classes, } = this.props; const { username, open } = this.state; @@ -107,10 +285,10 @@ class EditUserInClassroomButton extends Component { const trimmedUsername = username.trim(); const usernameValidity = isUsernameValid(trimmedUsername); - let errorProps = {}; + let usernameErrorProps = {}; if (!usernameValidity) { - errorProps = { - ...errorProps, + usernameErrorProps = { + ...usernameErrorProps, helperText: t('Username is not valid.'), error: true, }; @@ -130,6 +308,8 @@ class EditUserInClassroomButton extends Component { - + {this.renderUsernameInput(usernameErrorProps)} + {this.renderUserData()}