Skip to content
Merged
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
23 changes: 13 additions & 10 deletions client/src/pages/lab_management/audit_logs/AuditLogEntity.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { Link } from "@mui/material";
import { useNavigate, useParams } from "react-router-dom";
import { useCurrentUser } from "../../../common/CurrentUserProvider";
import { isManagerFor } from "../../../common/PrivilegeUtils";
import { useQuery } from "@apollo/client";
import { GET_ORG_BY_ID } from "../../../queries/organizationQueries";

interface AuditLogEntityProps {
entityCode: string;
Expand All @@ -28,6 +30,8 @@ function getEntityUrl(entityType: string, id: string, makerspaceID: string) {
return `/makerspace/${makerspaceID}/readers#id-${id}`;
case "makerspace":
return `/makerspace/${id}`;
case "organization":
return `/makerspace/${makerspaceID}/organizations?q=${id}`;
default:
return `/makerspace/${makerspaceID}/history`;
}
Expand All @@ -42,7 +46,7 @@ export default function AuditLogEntity({ entityCode }: AuditLogEntityProps) {

const [entityType, id, label] = entityCode.split(":");

let url = getEntityUrl(entityType, id, makerspaceID ?? "0");
let url = getEntityUrl(entityType, id, makerspaceID ?? "0" );

// If this would link to the readers page, but the current user is not a manager,
// fall back to the makerspace history instead of exposing a non-accessible link.
Expand All @@ -53,19 +57,18 @@ export default function AuditLogEntity({ entityCode }: AuditLogEntityProps) {
const [reveal, setReveal] = useState(entityType !== "conceal");

const toggleConcealment = () => {
setReveal(reveal => !reveal)
}
setReveal((reveal) => !reveal);
};

return (
<span>
{!reveal
? <Link onClick={toggleConcealment}>
Click to Reveal
</Link>
: <Link onClick={() => navigate(url)} sx={{ cursor: "pointer" }}>
{!reveal ? (
<Link onClick={toggleConcealment}>Click to Reveal</Link>
) : (
<Link onClick={() => navigate(url)} sx={{ cursor: "pointer" }}>
{label}
</Link>
}
)}
</span>
);
}
245 changes: 151 additions & 94 deletions client/src/pages/lab_management/organizations/OrganizationsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,52 +1,28 @@
import { Button, Card, Grid, IconButton, Stack, TextField, Typography } from "@mui/material";
import { useIsMobile } from "../../../common/IsMobileProvider";
import SearchBar from "../../../common/SearchBar";
import gql from "graphql-tag";

Check warning on line 4 in client/src/pages/lab_management/organizations/OrganizationsPage.tsx

View workflow job for this annotation

GitHub Actions / build

'gql' is defined but never used. Allowed unused vars must match /^_/u
import { useLazyQuery, useMutation } from "@apollo/client";
import { useLocation, useNavigate, useParams } from "react-router-dom";
import { useEffect, useState } from "react";
import RequestWrapper2 from "../../../common/RequestWrapper2";
import AddIcon from '@mui/icons-material/Add';
import AddIcon from "@mui/icons-material/Add";
import PrettyModal from "../../../common/PrettyModal";
import CloseIcon from '@mui/icons-material/Close';
import CloseIcon from "@mui/icons-material/Close";
import { CurrencyAccount } from "../currency/CurrencyAccounts";
import DeleteIcon from '@mui/icons-material/Delete';

const SEARCH_ORGS_LIMIT = gql`
query SearchOrganizationsLimit($searchText: String!) {
searchOrganizationsLimit(searchText: $searchText) {
id
username
displayname
accountID
account {
id
balance
}
}
}
`;

const CREATE_ORG = gql`
mutation CreateOrganization($username: String!, $displayname: String) {
createOrganization(username: $username, displayname: $displayname) {
id
}
}
`;

const DELETE_ORG = gql`
mutation DeleteOrganization($orgID: ID!) {
deleteOrganization(orgID: $orgID)
}
`;
import EditIcon from "@mui/icons-material/Edit";
import DeleteIcon from "@mui/icons-material/Delete";
import ThemedMarkdown from "../../../common/ThemedMarkdown";
import { toast } from "react-toastify";
import { SEARCH_ORGS_LIMIT, CREATE_ORG, EDIT_ORG_NOTES, DELETE_ORG } from "../../../queries/organizationQueries";

type Organization = {
id: number;
username: string;
displayname: string;
account: CurrencyAccount
}
notes: string;
account: CurrencyAccount;
};

export default function OrganizationsPage() {
const isMobile = useIsMobile();
Expand All @@ -55,14 +31,18 @@
const navigate = useNavigate();

const [getOrganizations, getOrganizationsResult] = useLazyQuery(SEARCH_ORGS_LIMIT);
const [createOrganization] = useMutation(CREATE_ORG, { refetchQueries: ["SearchOrganizationsLimit"] })
const [deleteOrganization] = useMutation(DELETE_ORG, { refetchQueries: ["SearchOrganizationsLimit"] })
const [createOrganization] = useMutation(CREATE_ORG, { refetchQueries: ["SearchOrganizationsLimit"] });
const [editOrganizationNotes] = useMutation(EDIT_ORG_NOTES, { refetchQueries: ["SearchOrganizationsLimit"] });
const [deleteOrganization] = useMutation(DELETE_ORG, { refetchQueries: ["SearchOrganizationsLimit"] });

const [searchText, setSearchText] = useState("");
const [open, setOpen] = useState(false);
const [openCreateNewOrg, setOpenCreateNewOrg] = useState(false);
const [openEditOrgNotes, setOpenEditOrgNotes] = useState(false);

const [username, setUsername] = useState("");
const [displayname, setDisplayname] = useState("");
const [notes, setNotes] = useState("");
const [orgID, setOrgID] = useState<number>(0);

const setUrlParam = (paramName: string, paramValue: string) => {
const params = new URLSearchParams(location.search);
Expand All @@ -89,14 +69,37 @@
return;
}

createOrganization({ variables: { username: username, displayname: displayname } });
handleExit();
createOrganization({ variables: { username: username, displayname: displayname, notes: notes } });
handleExitCreateNewOrg();
}

function handleExit() {
function handleExitCreateNewOrg() {
setUsername("");
setDisplayname("");
setOpen(false);
setNotes("");
setOpenCreateNewOrg(false);
}

function handleOpenEditOrgNotes(org: Organization) {
setOrgID(org.id);
setNotes(org.notes);
setOpenEditOrgNotes(true);
}
function handleEditedNotes() {
editOrganizationNotes({
variables: { orgID: orgID, notes: notes },
onCompleted: () => {
toast.success("Organization notes updated successfully");
handleExitEditOrgNotes();
},
onError: (error) => {
toast.error("Error updating organization notes: " + error.message);
}
});
}
function handleExitEditOrgNotes() {
setOrgID(0);
setNotes("");
setOpenEditOrgNotes(false);
}

const moneyForamtter = new Intl.NumberFormat("en-US", {
Expand All @@ -107,26 +110,26 @@
return (
<RequestWrapper2 result={getOrganizationsResult} render={(data) => {

const organizations: Organization[] = data.searchOrganizationsLimit;

return (
<Stack spacing={2} margin={"10px 20px"}>
<title>Organizations | Make @ RIT</title>
<Stack direction={isMobile ? "column" : "row"} spacing={2}>
<Typography variant="h4">Organizations</Typography>
<SearchBar
placeholder="Search Organizations"
sx={{ maxWidth: 300 }}
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
onClear={() => setUrlParam("q", "")}
onSubmit={() => setUrlParam("q", searchText)}
/>
<Button variant="contained" color="success" startIcon={<AddIcon />} onClick={() => setOpen(true)}>
New Organization
</Button>
</Stack>
<Grid container spacing={2} justifyContent={"center"}>
const organizations: Organization[] = data.searchOrganizationsLimit;

return (
<Stack spacing={2} margin={"10px 20px"}>
<title>Organizations | Make @ RIT</title>
<Stack direction={isMobile ? "column" : "row"} spacing={2}>
<Typography variant="h4">Organizations</Typography>
<SearchBar
placeholder="Search Organizations"
sx={{ maxWidth: 300 }}
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
onClear={() => setUrlParam("q", "")}
onSubmit={() => setUrlParam("q", searchText)}
/>
<Button variant="contained" color="success" startIcon={<AddIcon />} onClick={() => setOpenCreateNewOrg(true)}>
New Organization
</Button>
</Stack>
<Grid container spacing={2} justifyContent={"center"}>
{
organizations.map((org) => {

Expand All @@ -139,6 +142,12 @@
<Typography><b>Account ID:</b> {org.account.id}</Typography>
<Typography><b>Credits:</b> {moneyForamtter.format(org.account.balance / 100)}</Typography>
</Stack>
{org.notes.length > 0 && (
<Stack direction={"column"} alignItems={"center"}>
<Typography fontWeight={"bold"}>Notes:</Typography>
<ThemedMarkdown>{org.notes}</ThemedMarkdown>
</Stack>
)}
<Stack direction={"row"} justifyContent={"space-between"} width={"100%"}>
<Button
color="error"
Expand All @@ -149,11 +158,20 @@
Delete
</Button>

<Button
color="primary"
startIcon={<EditIcon />}
sx={{ alignSelf: "center" }}
onClick={() => handleOpenEditOrgNotes(org)}
>
Edit Notes
</Button>

<Button
color="secondary"
sx={{ alignSelf: "flex-end" }}
onClick={() => {
navigate(`/makerspace/${makerspaceID}/currency?a=${org.username}`)
navigate(`/makerspace/${makerspaceID}/currency?a=${org.username}`);
}}
>
View Account
Expand All @@ -165,49 +183,88 @@
);
})
}
</Grid>
<PrettyModal open={open} onClose={handleExit} width={"400px"}>
<Stack width={"100%"} spacing={2} alignItems={"center"}>
</Grid>
{/* Create New Org Modal */}
<PrettyModal open={openCreateNewOrg} onClose={handleExitCreateNewOrg} width={"400px"}>
<Stack width={"100%"} spacing={2} alignItems={"center"}>
<Stack direction={"row"} spacing={1} justifyContent={"space-between"} width={"100%"} alignItems={"center"}>
<Typography variant="h5">Create new Organization</Typography>
<IconButton color="error" onClick={handleExit}>
<CloseIcon />
</IconButton>
</Stack>
<Typography variant="h5">Create new Organization</Typography>
<IconButton color="error" onClick={handleExitCreateNewOrg}>
<CloseIcon />
</IconButton>
</Stack>

<TextField
label="Username"
fullWidth
required
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<TextField
label="Display Name"
fullWidth
value={displayname}
onChange={(e) => setDisplayname(e.target.value)}
/>
<Stack direction={"row"} justifyContent={"space-between"} width={"100%"}>
<TextField
label="Username"
fullWidth
required
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<TextField
label="Display Name"
fullWidth
value={displayname}
onChange={(e) => setDisplayname(e.target.value)}
/>
<TextField
label="Notes"
fullWidth
multiline
minRows={3}
value={notes}
onChange={(e) => setNotes(e.target.value)}
/>
<Stack direction={"row"} justifyContent={"space-between"} width={"100%"}>
<Button
variant="contained"
color="error"
onClick={handleExit}
onClick={handleExitCreateNewOrg}
>
Cancel
</Button>
Cancel
</Button>
<Button
variant="contained"
color="success"
onClick={handleNewOrg}
>
Submit
</Button>
Submit
</Button>
</Stack>
</Stack>
</Stack>
</PrettyModal >
</Stack >
);
</PrettyModal>

{/* Edit Org Notes Modal */}
<PrettyModal open={openEditOrgNotes} onClose={handleExitEditOrgNotes} width={"400px"}>
<Stack width={"100%"} spacing={2} alignItems={"center"}>
<Stack
direction={"row"}
spacing={1}
justifyContent={"space-between"}
width={"100%"}
alignItems={"center"}
>
<Typography variant="h5">Edit Organization Notes</Typography>
<IconButton color="error" onClick={handleExitEditOrgNotes}>
<CloseIcon />
</IconButton>
</Stack>
<TextField
label="Notes"
fullWidth
multiline
minRows={3}
value={notes}
onChange={(e) => setNotes(e.target.value)}
/>
<Stack direction={"row"} justifyContent={"space-between"} width={"100%"}>
<Button variant="contained" color="error" onClick={handleExitEditOrgNotes}>Cancel</Button>
<Button variant="contained" color="success" onClick={handleEditedNotes}>Submit</Button>
</Stack>
</Stack>
</PrettyModal>
</Stack>
);
}} />
);
}
Loading
Loading