Skip to content

Commit

Permalink
Appearance modifications to prepare for exam-group settings.
Browse files Browse the repository at this point in the history
  • Loading branch information
krulis-martin committed Jan 27, 2024
1 parent 0437e0c commit 966a04c
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 120 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,24 @@ import { GroupIcon, LoadingIcon } from '../../icons';
import Button from '../../widgets/TheButton';

const OrganizationalGroupButton = ({ organizational, pending, disabled = false, setOrganizational, ...props }) => (
<Button
{...props}
variant={disabled ? 'secondary' : 'info'}
onClick={setOrganizational(!organizational)}
disabled={pending || disabled}>
{pending ? <LoadingIcon gapRight /> : <GroupIcon organizational={!organizational} gapRight />}
{organizational ? (
<FormattedMessage id="app.organizationalGroupButton.unset" defaultMessage="Change to Regular Group" />
) : (
<FormattedMessage id="app.organizationalGroupButton.set" defaultMessage="Change to Organizational Group" />
)}
</Button>
<div className="text-center">
<strong>
<FormattedMessage id="app.organizationalGroupButton.label" defaultMessage="Change type to" />:
</strong>
<br />
<Button
{...props}
variant={disabled ? 'secondary' : 'info'}
onClick={setOrganizational(!organizational)}
disabled={pending || disabled}>
{pending ? <LoadingIcon gapRight /> : <GroupIcon organizational={!organizational} gapRight />}
{organizational ? (
<FormattedMessage id="app.organizationalGroupButton.unset" defaultMessage="Regular" />
) : (
<FormattedMessage id="app.organizationalGroupButton.set" defaultMessage="Organizational" />
)}
</Button>
</div>
);

OrganizationalGroupButton.propTypes = {
Expand Down
5 changes: 3 additions & 2 deletions src/components/icons/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ export const ExpandCollapseIcon = ({ isOpen = false, ...props }) =>
export const FailureIcon = props => <Icon className="text-danger" {...props} icon="times" />;
export const FaqIcon = props => <Icon {...props} icon={['far', 'question-circle']} />;
export const ForkIcon = props => <Icon {...props} icon="code-branch" />;
export const GroupIcon = ({ organizational = false, archived = false, ...props }) => (
<Icon {...props} icon={organizational ? 'sitemap' : archived ? 'archive' : 'users'} />
export const GroupIcon = ({ organizational = false, archived = false, exam = false, ...props }) => (
<Icon {...props} icon={organizational ? 'sitemap' : archived ? 'archive' : exam ? 'person-circle-check' : 'users'} />
);
export const HomeIcon = props => <Icon {...props} icon="home" />;
export const InfoIcon = props => <Icon {...props} icon="info-circle" />;
Expand Down Expand Up @@ -178,6 +178,7 @@ ExpandCollapseIcon.propTypes = {
GroupIcon.propTypes = {
organizational: PropTypes.bool,
archived: PropTypes.bool,
exam: PropTypes.bool,
};

ReviewIcon.propTypes = {
Expand Down
21 changes: 10 additions & 11 deletions src/components/layout/Navigation/Navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import GroupsNameContainer from '../../../containers/GroupsNameContainer';
import ShadowAssignmentNameContainer from '../../../containers/ShadowAssignmentNameContainer';
import UsersNameContainer from '../../../containers/UsersNameContainer';
import PipelineNameContainer from '../../../containers/PipelineNameContainer';
import { AssignmentIcon, ExerciseIcon, GroupIcon, PipelineIcon, ShadowAssignmentIcon } from '../../icons';
import { AssignmentIcon, ExerciseIcon, PipelineIcon, ShadowAssignmentIcon } from '../../icons';

import styles from './Navigation.less';

Expand Down Expand Up @@ -82,16 +82,15 @@ const Navigation = ({

{groupId && (
<span>
<OverlayTrigger
placement="bottom"
overlay={
<Tooltip id="groupIconTooltip">
<FormattedMessage id="app.navigation.group" defaultMessage="Group" />
</Tooltip>
}>
<GroupIcon gapRight className="text-muted" />
</OverlayTrigger>
<GroupsNameContainer groupId={groupId} fullName translations admins ancestorLinks />
<GroupsNameContainer
groupId={groupId}
fullName
translations
admins
ancestorLinks
showIcon
iconTooltip={<FormattedMessage id="app.navigation.group" defaultMessage="Group" />}
/>
</span>
)}

Expand Down
18 changes: 17 additions & 1 deletion src/containers/GroupsNameContainer/GroupsNameContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { groupSelector, groupAccessorSelector } from '../../redux/selectors/grou
import ResourceRenderer from '../../components/helpers/ResourceRenderer';
import { hasPermissions } from '../../helpers/common';
import GroupsName from '../../components/Groups/GroupsName';
import Icon, { LoadingIcon } from '../../components/icons';
import Icon, { GroupIcon, LoadingIcon } from '../../components/icons';
import OptionalTooltipWrapper from '../../components/widgets/OptionalTooltipWrapper';
import UsersNameContainer from '../UsersNameContainer';

class GroupsNameContainer extends Component {
Expand Down Expand Up @@ -37,6 +38,8 @@ class GroupsNameContainer extends Component {
ancestorLinks = false,
admins = false,
separator = <Icon icon="link" className="small half-opaque" largeGapLeft largeGapRight />,
showIcon = false,
iconTooltip = null,
} = this.props;
return (
<ResourceRenderer
Expand All @@ -49,6 +52,17 @@ class GroupsNameContainer extends Component {
}>
{group => (
<>
{showIcon && (
<OptionalTooltipWrapper tooltip={iconTooltip} hide={!iconTooltip} tooltipId={`groupIcon-${group.id}`}>
<GroupIcon
gapRight
className="text-muted"
organizational={group.organizational}
archived={group.archived}
exam={group.exam}
/>
</OptionalTooltipWrapper>
)}
{fullName &&
group.parentGroupsIds
.filter((_, idx) => idx > 0)
Expand Down Expand Up @@ -104,6 +118,8 @@ GroupsNameContainer.propTypes = {
noLoadAsync: PropTypes.bool,
group: ImmutablePropTypes.map,
groupAccessor: PropTypes.func.isRequired,
showIcon: PropTypes.bool,
iconTooltip: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
loadAsync: PropTypes.func.isRequired,
};

Expand Down
7 changes: 4 additions & 3 deletions src/locales/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -1220,8 +1220,9 @@
"app.numericTextField.validationFailedMax": "Hodnota nesmí být vyšší než {validateMax}.",
"app.numericTextField.validationFailedMin": "Hodnota nesmí být nižší než {validateMin}.",
"app.numericTextField.validationFailedMinMax": "Hodnota musí být v rozmezí {validateMin} a {validateMax}.",
"app.organizationalGroupButton.set": "Změnit na organizační skupinu",
"app.organizationalGroupButton.unset": "Změnit na běžnou skupinu",
"app.organizationalGroupButton.label": "Změnit typ na",
"app.organizationalGroupButton.set": "Organizační",
"app.organizationalGroupButton.unset": "Běžnou",
"app.page.failed": "Načtení stránky se nezdařilo",
"app.page.failedDescription.explain": "Tento problém mohl být způsoben výpadkem sítě nebo interní chybou na straně serveru. Rovněž je možné, že požadované datové objekty pro zobrazení této stránky byly smazány.",
"app.page.failedDescription.sorry": "Prosíme, zkuste to později. Omlouváme se za způsobené problémy. Pokud problém přetrvává ověřte, že zobrazovaný objekt stále existuje.",
Expand Down Expand Up @@ -2069,4 +2070,4 @@
"recodex-judge-shuffle-all": "Sudí neuspořádaných tokenů a řádků",
"recodex-judge-shuffle-newline": "Sudí neuspořádaných tokenů (ignorující konce řádků)",
"recodex-judge-shuffle-rows": "Sudí neuspořádaných řádků"
}
}
5 changes: 3 additions & 2 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1220,8 +1220,9 @@
"app.numericTextField.validationFailedMax": "The value must not be greater than {validateMax}.",
"app.numericTextField.validationFailedMin": "The value must not be lesser than {validateMin}.",
"app.numericTextField.validationFailedMinMax": "The value must be in between {validateMin} and {validateMax}.",
"app.organizationalGroupButton.set": "Change to Organizational Group",
"app.organizationalGroupButton.unset": "Change to Regular Group",
"app.organizationalGroupButton.label": "Change type to",
"app.organizationalGroupButton.set": "Organizational",
"app.organizationalGroupButton.unset": "Regular",
"app.page.failed": "Cannot load the page",
"app.page.failedDescription.explain": "This problem might have been caused by network failure or by internal error at server side. It is also possible that some of the resources required for displaying this page have been deleted.",
"app.page.failedDescription.sorry": "We are sorry for the inconvenience, please try again later. If the problem prevails, verify that the requested resource still exists.",
Expand Down
181 changes: 92 additions & 89 deletions src/pages/EditGroup/EditGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import RelocateGroupForm, { getPossibleParentsOfGroup } from '../../components/f
import OrganizationalGroupButtonContainer from '../../containers/OrganizationalGroupButtonContainer';
import ArchiveGroupButtonContainer from '../../containers/ArchiveGroupButtonContainer';
import DeleteGroupButtonContainer from '../../containers/DeleteGroupButtonContainer';
import Button from '../../components/widgets/TheButton';

Check failure on line 18 in src/pages/EditGroup/EditGroup.js

View workflow job for this annotation

GitHub Actions / lint (18)

'Button' is defined but never used
import Box from '../../components/widgets/Box';
import Callout from '../../components/widgets/Callout';
import GroupArchivedWarning from '../../components/Groups/GroupArchivedWarning/GroupArchivedWarning';
Expand Down Expand Up @@ -108,47 +109,30 @@ class EditGroup extends Component {

<GroupArchivedWarning {...group} groupsDataAccessor={groupsAccessor} linkFactory={GROUP_EDIT_URI_FACTORY} />

{hasPermissions(group, 'update') && !group.archived && (
<EditGroupForm
form="editGroup"
initialValues={this.getInitialValues(group)}
onSubmit={editGroup}
hasThreshold={hasThreshold}
isPublic={group.public}
isSuperAdmin={isSuperAdmin}
/>
)}

{canRelocate(group) && (
<ResourceRenderer resource={groups.toArray()} returnAsArray>
{groups =>
getPossibleParentsOfGroup(groups, group).length > 1 && (
<Box
type="warning"
title={<FormattedMessage id="app.editGroup.relocateGroup" defaultMessage="Relocate Group" />}>
<RelocateGroupForm
initialValues={getRelocateFormInitialValues(group)}
groups={getPossibleParentsOfGroup(groups, group)}
groupsAccessor={groupsAccessor}
onSubmit={relocateGroup}
/>
</Box>
)
}
</ResourceRenderer>
)}
<Row>
{hasPermissions(group, 'update') && !group.archived && (
<Col xs={12} xl={6}>
<EditGroupForm
form="editGroup"
initialValues={this.getInitialValues(group)}
onSubmit={editGroup}
hasThreshold={hasThreshold}
isPublic={group.public}
isSuperAdmin={isSuperAdmin}
/>
</Col>
)}

{hasPermissions(group, 'update') && (
<>
{!group.archived && (
<Col xs={12} xl={group.archived || !hasPermissions(group, 'update') ? 12 : 6}>
{!group.archived && hasPermissions(group, 'setOrganizational') && (
<Box
type="info"
title={<FormattedMessage id="app.editGroup.changeGroupType" defaultMessage="Change group type" />}>
<Row className="align-items-center">
<Col xs={false} sm="auto">
<OrganizationalGroupButtonContainer id={group.id} size="lg" className="m-2" locale={locale} />
<OrganizationalGroupButtonContainer id={group.id} className="m-2" locale={locale} />
</Col>
<Col xs={12} sm className="text-muted">
<Col xs={12} sm className="text-muted small">
<FormattedMessage
id="app.editGroup.organizationalExplain"
defaultMessage="Regular groups are containers for students and assignments. Organizational groups are intended to create hierarchy, so they are forbidden to hold any students or assignments."
Expand All @@ -166,9 +150,9 @@ class EditGroup extends Component {
}>
<Row className="align-items-center">
<Col xs={false} sm="auto">
<ArchiveGroupButtonContainer id={group.id} size="lg" className="m-2" onChange={reload} />
<ArchiveGroupButtonContainer id={group.id} shortLabels className="m-2" onChange={reload} />
</Col>
<Col xs={12} sm className="text-muted">
<Col xs={12} sm className="text-muted small">
<FormattedMessage
id="app.editGroup.archivedExplain"
defaultMessage="Archived groups are containers for students, assignments and results after the course is finished. They are immutable and can be accessed through separate Archive page."
Expand All @@ -177,63 +161,82 @@ class EditGroup extends Component {
</Row>
</Box>
)}
</>
)}

{hasPermissions(group, 'remove') && (
<Box
type="danger"
title={<FormattedMessage id="app.editGroup.deleteGroup" defaultMessage="Delete Group" />}>
<Row className="align-items-center">
<Col xs={false} sm="auto">
<DeleteGroupButtonContainer
id={group.id}
size="lg"
className="m-2"
disabled={
group.parentGroupId === null || (group.childGroups && group.childGroups.length > 0) // TODO whatabout archived sub-groups?
}
onDeleted={() =>
navigate(
canViewParentDetail
? GROUP_INFO_URI_FACTORY(group.parentGroupId)
: INSTANCE_URI_FACTORY(instanceId),
{ replace: true }
)
}
/>
</Col>
<Col xs={12} sm>
<div className="text-muted">
<FormattedMessage
id="app.editGroup.deleteGroupWarning"
defaultMessage="Deleting a group will make all attached entities (assignments, solutions, ...) inaccessible."
/>
</div>
{canRelocate(group) && (
<ResourceRenderer resource={groups.toArray()} returnAsArray>
{groups =>
getPossibleParentsOfGroup(groups, group).length > 1 && (
<Box
type="warning"
title={<FormattedMessage id="app.editGroup.relocateGroup" defaultMessage="Relocate Group" />}>
<RelocateGroupForm
initialValues={getRelocateFormInitialValues(group)}
groups={getPossibleParentsOfGroup(groups, group)}
groupsAccessor={groupsAccessor}
onSubmit={relocateGroup}
/>
</Box>
)
}
</ResourceRenderer>
)}

{group.parentGroupId === null && (
<div className="mt-1">
<WarningIcon className="text-danger" gapRight />
<FormattedMessage
id="app.editGroup.cannotDeleteRootGroup"
defaultMessage="This is a so-called root group and it cannot be deleted."
{hasPermissions(group, 'remove') && (
<Box
type="danger"
title={<FormattedMessage id="app.editGroup.deleteGroup" defaultMessage="Delete Group" />}>
<Row className="align-items-center">
<Col xs={false} sm="auto">
<DeleteGroupButtonContainer
id={group.id}
small={false}
className="m-2"
disabled={
group.parentGroupId === null || (group.childGroups && group.childGroups.length > 0) // TODO whatabout archived sub-groups?
}
onDeleted={() =>
navigate(
canViewParentDetail
? GROUP_INFO_URI_FACTORY(group.parentGroupId)
: INSTANCE_URI_FACTORY(instanceId),
{ replace: true }
)
}
/>
</div>
)}
</Col>
<Col xs={12} sm>
<div className="text-muted small">
<FormattedMessage
id="app.editGroup.deleteGroupWarning"
defaultMessage="Deleting a group will make all attached entities (assignments, solutions, ...) inaccessible."
/>
</div>

{group.parentGroupId !== null && group.childGroups && group.childGroups.length > 0 && (
<div className="mt-1">
<WarningIcon className="text-danger" gapRight />
<FormattedMessage
id="app.editGroup.cannotDeleteGroupWithSubgroups"
defaultMessage="Group with nested sub-groups cannot be deleted."
/>
</div>
)}
</Col>
</Row>
</Box>
)}
{group.parentGroupId === null && (
<div className="mt-1">
<WarningIcon className="text-danger" gapRight />
<FormattedMessage
id="app.editGroup.cannotDeleteRootGroup"
defaultMessage="This is a so-called root group and it cannot be deleted."
/>
</div>
)}

{group.parentGroupId !== null && group.childGroups && group.childGroups.length > 0 && (
<div className="mt-1">
<WarningIcon className="text-danger" gapRight />
<FormattedMessage
id="app.editGroup.cannotDeleteGroupWithSubgroups"
defaultMessage="Group with nested sub-groups cannot be deleted."
/>
</div>
)}
</Col>
</Row>
</Box>
)}
</Col>
</Row>
</div>
)}
</Page>
Expand Down

0 comments on commit 966a04c

Please sign in to comment.