diff --git a/src/components/Groups/GroupTree/GroupTree.js b/src/components/Groups/GroupTree/GroupTree.js index 82cbef8f5..a9f48bafa 100644 --- a/src/components/Groups/GroupTree/GroupTree.js +++ b/src/components/Groups/GroupTree/GroupTree.js @@ -1,12 +1,15 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { FormattedMessage } from 'react-intl'; +import { FormattedMessage, injectIntl } from 'react-intl'; +import { LinkContainer } from 'react-router-bootstrap'; import Icon from 'react-fontawesome'; + import Button from '../../widgets/FlatButton'; -import { LinkContainer } from 'react-router-bootstrap'; import { TreeView, TreeViewItem } from '../../widgets/TreeView'; import { isReady, getJsData } from '../../../redux/helpers/resourceManager'; import GroupsName from '../GroupsName'; +import { computeVisibleGroupsMap } from '../../helpers/group.js'; +import { getLocalizedResourceName } from '../../../helpers/getLocalizedData'; import withLinks from '../../../hoc/withLinks'; @@ -42,6 +45,33 @@ class GroupTree extends Component { ); }; + renderChildGroups = ( + { all: allChildGroups, public: publicChildGroups }, + visibleGroupsMap + ) => { + const { level = 0, isOpen = false, groups, intl: { locale } } = this.props; + return allChildGroups + .filter(id => visibleGroupsMap[id]) + .sort((id1, id2) => { + const name1 = getLocalizedResourceName(groups.get(id1), locale); + const name2 = getLocalizedResourceName(groups.get(id2), locale); + return name1 !== undefined && name2 !== undefined + ? name1.localeCompare(name2, locale) + : 0; + }) + .map(id => + = 0} + visibleGroupsMap={visibleGroupsMap} + /> + ); + }; + render() { const { id, @@ -50,6 +80,7 @@ class GroupTree extends Component { isPublic = true, groups, currentGroupId = null, + visibleGroupsMap = null, links: { GROUP_URI_FACTORY } } = this.props; @@ -62,10 +93,15 @@ class GroupTree extends Component { name, localizedTexts, canView, - childGroups: { all: allChildGroups, public: publicChildGroups }, + childGroups, primaryAdminsIds } = getJsData(group); + const actualVisibleGroupsMap = + visibleGroupsMap !== null + ? visibleGroupsMap + : computeVisibleGroupsMap(groups); + return ( {level !== 0 && @@ -78,8 +114,10 @@ class GroupTree extends Component { noLink /> } + id={id} level={level} admins={primaryAdminsIds} + isPublic={isPublic} isOpen={currentGroupId === id || isOpen} actions={ currentGroupId !== id && canView @@ -87,27 +125,10 @@ class GroupTree extends Component { : undefined } > - {allChildGroups.map(id => - = 0} - /> - )} + {this.renderChildGroups(childGroups, actualVisibleGroupsMap)} } {level === 0 && - allChildGroups.map(id => - = 0} - /> - )} + this.renderChildGroups(childGroups, actualVisibleGroupsMap)} ); } @@ -120,7 +141,9 @@ GroupTree.propTypes = { isOpen: PropTypes.bool, isPublic: PropTypes.bool, currentGroupId: PropTypes.string, - links: PropTypes.object + visibleGroupsMap: PropTypes.object, + links: PropTypes.object, + intl: PropTypes.shape({ locale: PropTypes.string.isRequired }).isRequired }; -export default withLinks(GroupTree); +export default withLinks(injectIntl(GroupTree)); diff --git a/src/components/forms/EditGroupForm/EditGroupForm.js b/src/components/forms/EditGroupForm/EditGroupForm.js index a1a394d80..7e502a79f 100644 --- a/src/components/forms/EditGroupForm/EditGroupForm.js +++ b/src/components/forms/EditGroupForm/EditGroupForm.js @@ -111,7 +111,7 @@ const EditGroupForm = ({ label={ } required diff --git a/src/components/helpers/LocalizedNames/LocalizedExerciseName.js b/src/components/helpers/LocalizedNames/LocalizedExerciseName.js index 89ba8e17b..7b870434a 100644 --- a/src/components/helpers/LocalizedNames/LocalizedExerciseName.js +++ b/src/components/helpers/LocalizedNames/LocalizedExerciseName.js @@ -28,7 +28,7 @@ const LocalizedExerciseName = ({ entity, intl: { locale } }) => { } > - +   } diff --git a/src/components/helpers/LocalizedNames/LocalizedGroupName.js b/src/components/helpers/LocalizedNames/LocalizedGroupName.js index df2bcb925..5135a8efb 100644 --- a/src/components/helpers/LocalizedNames/LocalizedGroupName.js +++ b/src/components/helpers/LocalizedNames/LocalizedGroupName.js @@ -28,7 +28,7 @@ const LocalizedGroupName = ({ entity, intl: { locale } }) => { } > - +   } diff --git a/src/components/helpers/group.js b/src/components/helpers/group.js new file mode 100644 index 000000000..d94733b06 --- /dev/null +++ b/src/components/helpers/group.js @@ -0,0 +1,15 @@ +import { defaultMemoize } from 'reselect'; + +export const computeVisibleGroupsMap = defaultMemoize(groups => { + const res = {}; + groups + .filter(group => group.getIn(['data', 'canView'])) + .forEach((group, id) => { + res[id] = true; + group.getIn(['data', 'parentGroupsIds'], []).forEach(parentId => { + res[parentId] = true; + }); + }); + + return res; +}); diff --git a/src/components/widgets/TreeView/TreeViewLeaf.js b/src/components/widgets/TreeView/TreeViewLeaf.js index 6d2a64801..1e35b3643 100644 --- a/src/components/widgets/TreeView/TreeViewLeaf.js +++ b/src/components/widgets/TreeView/TreeViewLeaf.js @@ -1,16 +1,20 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; +import { OverlayTrigger, Tooltip } from 'react-bootstrap'; import Icon from 'react-fontawesome'; + import { LoadingIcon } from '../../icons'; import LevelGap from './LevelGap'; import GroupsName from '../../../components/Groups/GroupsName'; import UsersNameContainer from '../../../containers/UsersNameContainer'; const TreeViewLeaf = ({ + id, loading = false, title, admins, + isPublic, icon = 'square-o', onClick, level, @@ -39,16 +43,36 @@ const TreeViewLeaf = ({ ) } + {isPublic && + + + + } + > + + } {actions} ; TreeViewLeaf.propTypes = { + id: PropTypes.string, loading: PropTypes.bool, title: PropTypes.oneOfType([ PropTypes.string, PropTypes.shape({ type: PropTypes.oneOf([FormattedMessage, GroupsName]) }) ]).isRequired, admins: PropTypes.array, + isPublic: PropTypes.bool, icon: PropTypes.string, onClick: PropTypes.func, level: PropTypes.number.isRequired, diff --git a/src/helpers/getLocalizedData.js b/src/helpers/getLocalizedData.js index 143017807..412791ebc 100644 --- a/src/helpers/getLocalizedData.js +++ b/src/helpers/getLocalizedData.js @@ -5,9 +5,21 @@ const getLocalizedX = field => (entity, locale) => { return localizedText ? localizedText[field] : entity[field]; }; +const getLocalizedResourceX = field => (resource, locale) => { + const localizedTexts = resource && resource.getIn(['data', 'localizedTexts']); + const localizedText = + localizedTexts && + localizedTexts.find(text => text.get('locale') === locale); + return localizedText + ? localizedText.get(field) + : resource ? resource.getIn(['data', field]) : undefined; +}; + export const getLocalizedName = getLocalizedX('name'); export const getLocalizedDescription = getLocalizedX('description'); +export const getLocalizedResourceName = getLocalizedResourceX('name'); + export const getOtherLocalizedNames = (entity, locale) => { const name = getLocalizedName(entity, locale); return entity.localizedTexts diff --git a/src/locales/cs.json b/src/locales/cs.json index 077b5cd92..d91ca9912 100644 --- a/src/locales/cs.json +++ b/src/locales/cs.json @@ -141,8 +141,8 @@ "app.confirm.no": "Ne", "app.confirm.yes": "Ano", "app.createGroup.externalId": "Externí identifikátor skupiny (například ID ze školního informačního systému):", - "app.createGroup.hasThreshold": "Students require cetrain number of points to complete the course", - "app.createGroup.isPublic": "Studenti se mohou sami přidávat k této skupině", + "app.createGroup.hasThreshold": "Studenti potřebují určitý počet bodů pro splnění kurzu", + "app.createGroup.isPublic": "Veřejná (skupinu vidí všichni uživatelé a můžou se do ní přidat)", "app.createGroup.publicStats": "Studenti mohou vidět dosažené body ostatních", "app.createGroup.threshold": "Minimální procentuální hranice potřebná ke splnění tohoto kurzu:", "app.createGroup.validation.thresholdBetweenZeroHundred": "Procentuální hranice musí být celé číslo od 0 do 100.", @@ -685,6 +685,7 @@ "app.groupResultsTableRow.noResults": "Nyní zde nejsou žádné výsledky k zobrazení.", "app.groupTree.detailButton": "Zobrazit stránku skupiny", "app.groupTree.loading": "Načítání...", + "app.groupTree.treeViewLeaf.publicTooltip": "The group is public", "app.groups.joinGroupButton": "Stát se členem", "app.groups.leaveGroupButton": "Opustit skupinu", "app.groups.makeGroupAdminButton": "Změnit na správce skupiny", diff --git a/src/locales/en.json b/src/locales/en.json index 8f0d8b39a..64e8e3082 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -142,7 +142,7 @@ "app.confirm.yes": "Yes", "app.createGroup.externalId": "External ID of the group (e. g. ID of the group in the school IS):", "app.createGroup.hasThreshold": "Students require cetrain number of points to complete the course", - "app.createGroup.isPublic": "Students can join the group themselves", + "app.createGroup.isPublic": "Public (everyone can see and join this group)", "app.createGroup.publicStats": "Students can see statistics of each other", "app.createGroup.threshold": "Minimum percent of the total points count needed to complete the course:", "app.createGroup.validation.thresholdBetweenZeroHundred": "Threshold must be an integer in between 0 and 100.", @@ -685,6 +685,7 @@ "app.groupResultsTableRow.noResults": "There are currently no results available.", "app.groupTree.detailButton": "See group's page", "app.groupTree.loading": "Loading ...", + "app.groupTree.treeViewLeaf.publicTooltip": "The group is public", "app.groups.joinGroupButton": "Join group", "app.groups.leaveGroupButton": "Leave group", "app.groups.makeGroupAdminButton": "Make group admin",