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: 16 additions & 7 deletions client/src/pages/lab_management/users/AccessCheckCard.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Button, Card, CardActions } from "@mui/material";
import { Button, Card, CardActions, Stack } from "@mui/material";
import { gql } from "@apollo/client";
import { useMutation } from "@apollo/client/react";
import AuditLogEntity from "../audit_logs/AuditLogEntity";
import { GET_USER } from "../../../queries/userQueries";
import { AccessCheckExtraInfo, GET_USER } from "../../../queries/userQueries";
import { useIsMobile } from "../../../common/IsMobileProvider";
import CheckIcon from '@mui/icons-material/Check';
import CloseIcon from '@mui/icons-material/Close';

const APPROVE_CHECK = gql`
mutation ApproveAccessCheck($id: ID!) {
Expand Down Expand Up @@ -43,18 +45,25 @@ export default function AccessCheckCard({ accessCheck, userID }: AccessCheckCard
return (
<Card
sx={{
backgroundColor: !approved ? (localStorage.getItem("themeMode") === "dark" ? "grey.900" : "grey.100") : (localStorage.getItem("themeMode") === "dark" ? "lightGreen.800" : "lightGreen.100"),
border: `2px solid ${!approved ? "lightgrey" : "palegreen"}`,
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
flexWrap: isMobile ? 'wrap' : 'nowrap',
alignItems: 'center',
padding: "5px"
}}
variant={approved ? undefined : "outlined"}
>
<div style={{ width: isMobile ? "100%" : "40%", marginLeft: "10px" }}>
<AuditLogEntity entityCode={"equipment:" + accessCheck.equipment.id + ":" + ((accessCheck.equipment !== undefined) ? accessCheck.equipment.name : "Loading...")}></AuditLogEntity>
</div>
<Stack sx={{ paddingLeft: "10px" }} direction={"row"} spacing={1} alignItems={"center"}>
{
approved
? <CheckIcon color="success" />
: <CloseIcon color="error" />
}
<AuditLogEntity
entityCode={"equipment:" + accessCheck.equipment.id + ":" + ((accessCheck.equipment !== undefined) ? accessCheck.equipment.name : "Loading...")}
/>
</Stack>
<CardActions>
{!approved && (
<Button
Expand Down
3 changes: 1 addition & 2 deletions client/src/pages/lab_management/users/HoldCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ export default function HoldCard({ hold, userID }: HoldCardProps) {
return (
<Card
sx={{
backgroundColor: removed ? (localStorage.getItem("themeMode") === "dark" ? "grey.900" : "grey.100") : (localStorage.getItem("themeMode") === "dark" ? "#150000" : "#fff8f8"),
border: `1px solid ${removed ? "grey" : "red"}`,
}}
>
Expand All @@ -42,7 +41,7 @@ export default function HoldCard({ hold, userID }: HoldCardProps) {
</Typography>
)}
<CardActions sx={{ px: 2 }}>
<Stack sx={{ flex: 1, color: "#595959" }}>
<Stack sx={{ flex: 1 }}>
<Typography variant="body2">
Placed by {`${hold.creator.firstName} ${hold.creator.lastName}`} on{" "}
{format(parseISO(hold.createDate), "M/d/yy h:mmaaa")}
Expand Down
21 changes: 12 additions & 9 deletions client/src/pages/lab_management/users/TrainerEquipmentSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { useQuery } from "@apollo/client/react";
import { useCurrentUser } from "../../../common/CurrentUserProvider"
import { GET_EQUIPMENTS } from "../../../queries/equipmentQueries";
import { MenuItem, Select } from "@mui/material";
import { FormControl, InputLabel, MenuItem, Select } from "@mui/material";
import RequestWrapper2 from "../../../common/RequestWrapper2";
import { isManagerFor } from "../../../common/PrivilegeUtils";

interface TrainerEquipmentSelectProps {
user: any;
setAddTrainerPerms: (id: number)=>void;
setAddTrainerPerms: (id: number) => void;
}

export default function TrainerEquipmentSelect(props: TrainerEquipmentSelectProps) {
Expand All @@ -24,13 +24,16 @@ export default function TrainerEquipmentSelect(props: TrainerEquipmentSelectProp
const sortedEquipment = possibleEquipments.sort((a, b) => (a.name.toLowerCase().localeCompare(b.name.toLowerCase())));

return (
<Select id="add-trainer-permissions" label="Equipment" fullWidth onChange={(e) => props.setAddTrainerPerms(Number(e.target.value))}>
{
sortedEquipment.map((equipment) => {
return <MenuItem value={equipment.id}>{equipment.name} ID: {equipment.id}</MenuItem>
})
}
</Select>
<FormControl fullWidth>
<InputLabel id="add-trainer-permissions">Equipment</InputLabel>
<Select id="add-trainer-permissions" label="Equipment" fullWidth onChange={(e) => props.setAddTrainerPerms(Number(e.target.value))}>
{
sortedEquipment.map((equipment) => {
return <MenuItem value={equipment.id}>{equipment.name} ID: {equipment.id}</MenuItem>
})
}
</Select>
</FormControl>
);
}} />
);
Expand Down
13 changes: 8 additions & 5 deletions client/src/pages/lab_management/users/UserPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ import AccessChecks from "./userpage/AccessChecks";
import Trainings from "./userpage/Trainings";
import Info from "./userpage/Info";
import Notes from "./userpage/Notes";
import { useMakeTheme } from "../../../common/MakeThemeProvider";

export default function UserPage() {
const { userID} = useParams<{ userID: string }>();
const { userID } = useParams<{ userID: string }>();
const isMobile = useIsMobile();
const makeTheme = useMakeTheme();

const [getUser, getUserResult] = useLazyQuery(GET_USER);

Expand All @@ -30,15 +32,16 @@ export default function UserPage() {
result={getUserResult}
render={({ user }) => {
return (
<Stack>
<Stack spacing={4}>
<title>{`${user.firstName} ${user.lastName} | ${makeTheme.title}`}</title>
<Info user={user} />
<Stack direction={isMobile ? "column" : "row"} width="100%" mt={4} spacing={4} justifyContent="center">
<Stack width="50%">
<Stack direction={isMobile ? "column" : "row"} width="100%" spacing={4} justifyContent="center">
<Stack width={isMobile ? "100%" : "50%"}>
<AccessChecks user={user} />
<Trainings user={user} />
</Stack>

<Stack width="50%">
<Stack width={isMobile ? "100%" : "50%"}>
<HoldsRestrictions user={user} />
<PrivilegeControl user={user} isMobile={isMobile} />
<CardTagSettings userID={user.id} hasCardTag={(user.cardTagID != null && user.cardTagID !== "")} />
Expand Down
41 changes: 29 additions & 12 deletions client/src/pages/lab_management/users/userpage/AccessChecks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { AccessCheckExtraInfo, GET_USER } from "../../../../queries/userQueries"
import { useState } from "react";
import AccessCheckCard from "../AccessCheckCard";
import CreateAccessCheckModal from "./CreateAccessCheckModal";
import SearchBar from "../../../../common/SearchBar";
import { useIsMobile } from "../../../../common/IsMobileProvider";

const REFRESH_CHECKS = gql`
mutation RefreshAccessChecks($userID: ID!) {
Expand All @@ -21,8 +23,10 @@ interface AccessCheckProps {

export default function AccessChecks(props: AccessCheckProps) {
const currentUser = useCurrentUser();
const isMobile = useIsMobile();

const [createAccessCheckModal, setCreateAccessCheckModal] = useState(false);
const [searchText, setSearchtext] = useState("");

const [refreshCheck, refreshCheckResult] = useMutation(REFRESH_CHECKS, { variables: { userID: props.user.id }, refetchQueries: [{ query: GET_USER, variables: { id: props.user.id } }] });

Expand All @@ -34,24 +38,37 @@ export default function AccessChecks(props: AccessCheckProps) {
)
);

const searchedACs: AccessCheckExtraInfo[] = filteredACs.filter(
(ac: AccessCheckExtraInfo) => (
ac.equipment.name.toLowerCase().includes(searchText.toLowerCase()) || ac.equipment.subName.toLowerCase().includes(searchText.toLowerCase())
)
)

return (
<Stack >
<Typography variant="h6" component="div" mb={1}>
Access Checks
</Typography>

<Stack direction={"row"} spacing={1}>
<ActionButton iconSize={5} color="info" appearance={"small"} variant="outlined" handleClick={async () => { refreshCheck() }} loading={refreshCheckResult.loading} buttonText="Refresh Checks" tooltipText="Purge all unapproved checks and repopulate based on currently passed modules." />
{isManager(currentUser) && <ActionButton iconSize={5} color="primary" appearance={"small"} variant="outlined" handleClick={async () => { setCreateAccessCheckModal(true) }} loading={false} buttonText="Create Check" />}
<Stack spacing={1}>
<Stack direction={isMobile ? "column" : "row"} justifyContent={"space-between"}>
<Typography variant="h6" component="div">
Access Checks
</Typography>
<Stack direction={"row"} spacing={1}>
{isManager(currentUser) && <ActionButton iconSize={5} color="primary" appearance={"small"} variant="outlined" handleClick={async () => { setCreateAccessCheckModal(true) }} loading={false} buttonText="Create Check" />}
<ActionButton iconSize={5} color="info" appearance={"small"} variant="outlined" handleClick={async () => { refreshCheck() }} loading={refreshCheckResult.loading} buttonText="Refresh Checks" tooltipText="Purge all unapproved checks and repopulate based on currently passed modules." />
</Stack>
</Stack>

<Stack spacing={1} mt={2}>
{filteredACs != null && filteredACs.map((accessCheck: AccessCheckExtraInfo) => (
<SearchBar
onChange={(e) => setSearchtext(e.target.value)}
onClear={() => setSearchtext("")}
sx={{
width: "100%"
}}
/>
<Stack spacing={1}>
{searchedACs != null && searchedACs.map((accessCheck: AccessCheckExtraInfo) => (
<AccessCheckCard key={accessCheck.id} accessCheck={accessCheck} userID={props.user.id} />
))}
</Stack>

{(filteredACs == null || (filteredACs.length === 0)) && (
{(searchedACs == null || (searchedACs.length === 0)) && (
<Alert severity="info">No Access Checks Available</Alert>
)}

Expand Down
157 changes: 79 additions & 78 deletions client/src/pages/lab_management/users/userpage/HoldsRestrictions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,86 +78,87 @@ export default function HoldsRestrictions(props: HoldsRestrictionsProps) {


return (
<Stack>
<Stack direction="row" justifyContent={"space-between"} mb={1}>
<Typography variant="h6">
Account Holds
</Typography>
<Button
color="error"
variant="contained"
onClick={handlePlaceHoldClicked}
startIcon={<BlockIcon />}
disabled={!isStaff(currentUser)}
>
Place hold
</Button>
</Stack>

{props.user.holds.length === 0 && (
<Alert severity="success">No Holds!</Alert>
)}

<Stack spacing={2}>
{props.user.holds.map((hold: Hold) => (
<HoldCard key={hold.id} hold={hold} userID={props.user.id} />
))}
</Stack>


<Typography variant="h6" component="div" mt={2} mb={1}>
Account Restrictions
</Typography>

{props.user.restrictions.length === 0 && (
<Alert severity="success">No Restrictions!</Alert>
)}

<Stack spacing={2}>
{props.user.restrictions.map((restriction: Restriction) => (
<RestrictionCard key={restriction.id} restriction={restriction} userID={props.user.id} />
))}
<Stack spacing={3}>
<Stack spacing={1}>
<Stack direction="row" justifyContent={"space-between"}>
<Typography variant="h6">
Account Holds
</Typography>
<Button
color="error"
variant="contained"
onClick={handlePlaceHoldClicked}
startIcon={<BlockIcon />}
disabled={!isStaff(currentUser)}
>
Place hold
</Button>
</Stack>

{props.user.holds.length === 0 && (
<Alert severity="success">No Holds!</Alert>
)}

<Stack spacing={2}>
{props.user.holds.map((hold: Hold) => (
<HoldCard key={hold.id} hold={hold} userID={props.user.id} />
))}
</Stack>
</Stack>

{
isStaff(currentUser)
? < RequestWrapper2 result={getMakerspacesResult} render={(data) => {

const fullSpaces: FullMakerspace[] = data.makerspaces;
// I hate typescript I hate typescript I hate typescript I hate typescript I hate typescript I hate typescript I hate typescript I hate typescript
const potentialRestrictions = fullSpaces.filter((space: FullMakerspace) => isStaffFor(currentUser, Number(space.id)))

return (
<Stack direction="row" spacing={1} mt={2} alignItems={"center"}>
<FormControl fullWidth>
<InputLabel id="restriction-makerspace">Makerspace</InputLabel>
<Select id="restriction-makerspace"
label="Makerspace"
onChange={(e) => setRestrictionMakerspace(Number(e.target.value))}
fullWidth
<Stack spacing={1}>
<Typography variant="h6" component="div" mt={2} mb={1}>
Account Restrictions
</Typography>
{
isStaff(currentUser)
? < RequestWrapper2 result={getMakerspacesResult} render={(data) => {

const fullSpaces: FullMakerspace[] = data.makerspaces;
// I hate typescript I hate typescript I hate typescript I hate typescript I hate typescript I hate typescript I hate typescript I hate typescript
const potentialRestrictions = fullSpaces.filter((space: FullMakerspace) => isStaffFor(currentUser, Number(space.id)))

return (
<Stack direction="row" spacing={1} mt={2} alignItems={"center"}>
<FormControl fullWidth>
<InputLabel id="restriction-makerspace">Makerspace</InputLabel>
<Select id="restriction-makerspace"
label="Makerspace"
onChange={(e) => setRestrictionMakerspace(Number(e.target.value))}
fullWidth
>
{
potentialRestrictions.map((space: FullMakerspace) => (
<MenuItem value={space.id}>{space.name} ID: {space.id}</MenuItem>
))
}
</Select>
</FormControl>
<Button
variant="contained"
color="warning"
onClick={handleCreateRestriction}
startIcon={<LockIcon />}
sx={{ whiteSpace: "nowrap", minWidth: "unset", height: "100%" }}
>
{
potentialRestrictions.map((space: FullMakerspace) => (
<MenuItem value={space.id}>{space.name} ID: {space.id}</MenuItem>
))
}
</Select>
</FormControl>
<Button
variant="contained"
color="error"
onClick={handleCreateRestriction}
startIcon={<LockIcon />}
sx={{ whiteSpace: "nowrap", minWidth: "unset" }}
>
Place Restriction
</Button>
</Stack>
);
}
} />
: null
}
Place Restriction
</Button>
</Stack>
);
}
} />
: null
}

{props.user.restrictions.length === 0 && (
<Alert severity="success" variant="filled">No Restrictions!</Alert>
)}

<Stack spacing={2}>
{props.user.restrictions.map((restriction: Restriction) => (
<RestrictionCard key={restriction.id} restriction={restriction} userID={props.user.id} />
))}
</Stack>
</Stack>
</Stack>
)
}
Loading
Loading