Skip to content

Commit

Permalink
show users page for org managers
Browse files Browse the repository at this point in the history
  • Loading branch information
aaronshiel committed Oct 3, 2024
1 parent d9f35b8 commit b5c17d0
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 55 deletions.
61 changes: 40 additions & 21 deletions client/src/components/nav-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Permission to use, copy, modify, and distribute this software and its documentat
The full terms of this copyright and license should always be found in the root directory of this software deliverable as "license.txt" and if these terms are not found with this software, please contact the USC Stevens Center for the full license.
*/
import { navigate } from "gatsby";
import React from "react";
import React, { useMemo } from "react";
import {
AppBar,
Button,
Expand Down Expand Up @@ -47,14 +47,15 @@ import { useWithLogin } from "store/slices/login/useWithLogin";
import useActiveMentor from "store/slices/mentor/useActiveMentor";
import withLocation from "wrap-with-location";
import { Mentor, UploadTask } from "types";
import { canEditContent, isAdmin, launchMentor } from "helpers";
import { canEditContent, isAdmin, launchMentor, managesOrg } from "helpers";
import {
areAllTasksDone,
isATaskCancelled,
} from "hooks/graphql/upload-status-helpers";
import { useWithImportStatus } from "hooks/graphql/use-with-import-status";
import ImportInProgressDialog from "./import-export/import-in-progress";
import { useAppSelector } from "store/hooks";
import { useWithOrganizations } from "hooks/graphql/use-with-organizations";

const useStyles = makeStyles({ name: { Login } })((theme: Theme) => ({
toolbar: {
Expand Down Expand Up @@ -177,10 +178,11 @@ function NavMenu(props: {
classes: Record<string, string>;
mentorSubjectsLocked: boolean;
onNav?: (cb: () => void) => void;
managesOrgs: boolean;
}): JSX.Element {
const { classes, mentorSubjectsLocked } = props;
const { classes, mentorSubjectsLocked, managesOrgs } = props;
const { logout, state } = useWithLogin();
const editPermission = canEditContent(state.user);
const isSuperManagerPlus = canEditContent(state.user);

function onLogout(): void {
logout();
Expand Down Expand Up @@ -249,29 +251,21 @@ function NavMenu(props: {
<ListItemText primary="Chat with Mentor" />
</ListItem>
<Divider style={{ marginTop: 15 }} />
{editPermission ? (

{isSuperManagerPlus || managesOrgs ? (
<ListSubheader className={classes.menuHeader}>
Management Tools
</ListSubheader>
) : undefined}

{isSuperManagerPlus ? (
<>
<ListSubheader className={classes.menuHeader}>
Management Tools
</ListSubheader>
<NavItem
text={"Create/Edit Subjects"}
link={"/author/subjects"}
icon={<EditIcon />}
onNav={props.onNav}
/>
<NavItem
text={"Users"}
link={"/users"}
icon={<PersonIcon />}
onNav={props.onNav}
/>
<NavItem
text={"Organizations"}
link={"/organizations"}
icon={<GroupIcon />}
onNav={props.onNav}
/>
<NavItem
text={"Config"}
link={"/config"}
Expand All @@ -284,7 +278,22 @@ function NavMenu(props: {
icon={<LRSIcon />}
onNav={props.onNav}
/>

</>
) : undefined}
{isSuperManagerPlus || managesOrgs ? (
<>
<NavItem
text={"Users"}
link={"/users"}
icon={<PersonIcon />}
onNav={props.onNav}
/>
<NavItem
text={"Organizations"}
link={"/organizations"}
icon={<GroupIcon />}
onNav={props.onNav}
/>
<Divider style={{ marginTop: 15 }} />
</>
) : undefined}
Expand Down Expand Up @@ -321,6 +330,8 @@ export function NavBar(props: {
} = props;
const { getData } = useActiveMentor();
const mentor: Mentor | undefined = getData((state) => state.data);
const accessToken = useAppSelector((state) => state.login.accessToken);
const { data: orgs } = useWithOrganizations(accessToken || "");
const user = useAppSelector((state) => state.login.user);
const lockedToConfig = mentor?.lockedToConfig && !isAdmin(user);
const importStatus = useWithImportStatus();
Expand All @@ -331,6 +342,13 @@ export function NavBar(props: {
).length || 0;
const numUploadsComplete =
uploads?.filter((upload) => areAllTasksDone(upload)).length || 0;
const managedOrgs = useMemo(() => {
const orgNodes = orgs?.edges.map((e) => e.node) || [];
if (!user) {
return [];
}
return orgNodes.filter((org) => managesOrg(org, user));
}, [orgs, Boolean(user)]);

const [isDrawerOpen, setIsDrawerOpen] = React.useState(false);
//if there are no uploads, defaults to true, else if there are any uploads that aren't yet cancelled, should not be disabled
Expand Down Expand Up @@ -411,6 +429,7 @@ export function NavBar(props: {
classes={classes}
mentorId={props.mentorId}
onNav={onNav}
managesOrgs={managedOrgs.length > 0}
/>
</SwipeableDrawer>
{checkForImportTask && importTask ? (
Expand Down
7 changes: 6 additions & 1 deletion client/src/components/users/table-footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,11 @@ export function getTableColumns(
}
if (!isAdmin) {
columns = columns.filter(
(c) => c.label !== "Disabled" && c.label !== "Disabled"
(c) =>
c.label !== "Disabled" &&
c.label !== "Disabled" &&
c.label !== "Locked" &&
c.label !== "Advanced"
);
}
return columns;
Expand All @@ -163,6 +167,7 @@ export function TableFooter(props: {
const hasPrev = userPagin.pageData?.pageInfo.hasPreviousPage || false;
const pageSizes = [10, 20, 50, 100];

// TODO: create dropdown that allows us to select an org
return (
<AppBar position="sticky" color="default" className={styles.appBar}>
<Toolbar>
Expand Down
3 changes: 2 additions & 1 deletion client/src/components/users/user-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
GetApp as GetAppIcon,
Launch as LaunchIcon,
} from "@mui/icons-material";
import { v4 as uuid } from "uuid";
const useStyles = makeStyles({ name: { UserItem } })((theme: Theme) => ({
dropdown: {
width: 170,
Expand Down Expand Up @@ -328,7 +329,7 @@ export function UserItem(props: {
.toLocaleString()
.split(",")
.map((s) => {
return <div key={mentor.updatedAt}>{s}</div>;
return <div key={`${mentor.updatedAt}${uuid()}`}>{s}</div>;
})}
</>
) : (
Expand Down
42 changes: 31 additions & 11 deletions client/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,21 +336,20 @@ export function canEditMentor(
if (!mentor || !user) {
return false;
}
const ops = mentor.orgPermissions?.filter(
const orgPermsThatManageMentor = mentor.orgPermissions?.filter(
(op) =>
op.editPermission === OrgEditPermissionType.MANAGE ||
op.editPermission === OrgEditPermissionType.ADMIN
);
if (ops) {
const os = ops.map((op) => op.orgId);
for (const org of orgs.filter((o) => os.includes(o._id))) {
if (
org.members.find(
(m) =>
m.user._id === user._id &&
(m.role === UserRole.ADMIN || m.role === UserRole.CONTENT_MANAGER)
)
) {
if (orgPermsThatManageMentor) {
const orgIdsThatManageMentor = orgPermsThatManageMentor.map(
(op) => op.orgId
);
const orgsThatManageMentor = orgs.filter((o) =>
orgIdsThatManageMentor.includes(o._id)
);
for (const org of orgsThatManageMentor) {
if (managesOrg(org, user)) {
return true;
}
}
Expand Down Expand Up @@ -502,3 +501,24 @@ export function isAdmin(user?: User): boolean {
user?.userRole === UserRole.ADMIN || user?.userRole === UserRole.SUPER_ADMIN
);
}

export function managesOrg(org: Organization, user: User): boolean {
return Boolean(
org.members.find(
(m) =>
m.user._id === user._id &&
(m.role === UserRole.ADMIN || m.role === UserRole.CONTENT_MANAGER)
)
);
}

export function isOrgMember(org: Organization, user: User): boolean {
const usersOrgPermissions = user.defaultMentor.orgPermissions.find(
(op) => op.orgId === org._id
);
return Boolean(
org.members.find((m) => m.user._id === user._id) ||
usersOrgPermissions?.editPermission === OrgEditPermissionType.ADMIN ||
usersOrgPermissions?.editPermission === OrgEditPermissionType.MANAGE
);
}
36 changes: 15 additions & 21 deletions client/src/pages/users.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import NavBar from "components/nav-bar";
import { ErrorDialog, LoadingDialog } from "components/dialog";
import { useWithUsers } from "hooks/graphql/use-with-users";
import withAuthorizationOnly from "hooks/wrap-with-authorization-only";
import { OrgEditPermissionType, User } from "types";
import { User } from "types";
import withLocation from "wrap-with-location";
import { canEditMentor } from "../helpers";
import { canEditMentor, isOrgMember } from "../helpers";
import useActiveMentor from "store/slices/mentor/useActiveMentor";
import { useWithOrganizations } from "hooks/graphql/use-with-organizations";
import { UsersTable } from "components/users/users-table";
Expand All @@ -31,18 +31,24 @@ const useStyles = makeStyles({ name: { UsersPage } })(() => ({
}));

function UsersPage(props: { accessToken: string; user: User }): JSX.Element {
const userPagin = useWithUsers(props.accessToken);
const orgsPagin = useWithOrganizations(props.accessToken);
const { accessToken } = props;
const userPagin = useWithUsers(accessToken);
const orgsPagin = useWithOrganizations(accessToken);
const { classes: styles } = useStyles();
const { switchActiveMentor } = useActiveMentor();
const [viewArchivedMentors, setViewArchivedMentors] =
useState<boolean>(false);
const [viewUnapprovedMentors, setViewUnapprovedMentors] =
useState<boolean>(false);
const orgs = orgsPagin.data?.edges.map((e) => e.node) || [];
const [selectedOrg, setSelectedOrg] = useState<string>("");

useEffect(() => {
const params = new URLSearchParams(location.search);

const orgParam = params.get("org");
setSelectedOrg(orgParam || "");

const viewUnapprovedMentors = params.get("unapproved");
if (viewUnapprovedMentors === "true") {
setViewUnapprovedMentors(true);
Expand Down Expand Up @@ -87,25 +93,13 @@ function UsersPage(props: { accessToken: string; user: User }): JSX.Element {
if (!viewArchivedMentors && u.defaultMentor.isArchived) {
return false;
}
const params = new URLSearchParams(location.search);
// filter users related to org only (member or gave permission to org)
if (params.get("org")) {
const org = orgs.find((o) => o._id === params.get("org"));
if (selectedOrg) {
const org = orgs.find((o) => o._id === selectedOrg);
if (org) {
const isMember = org.members.find((m) => m.user._id === u._id);
if (isMember) {
return true;
}
const hasOrgPerms = u.defaultMentor.orgPermissions.find(
(op) => op.orgId === org._id
const _isOrgMemeber = isOrgMember(org, u);
return (
_isOrgMemeber && canEditMentor(u.defaultMentor, props.user, [org])
);
if (hasOrgPerms) {
return (
hasOrgPerms.editPermission === OrgEditPermissionType.ADMIN ||
hasOrgPerms.editPermission === OrgEditPermissionType.MANAGE
);
}
return false;
}
}
return canEditMentor(u.defaultMentor, props.user, orgs);
Expand Down

0 comments on commit b5c17d0

Please sign in to comment.