Skip to content

Commit 75b2ad1

Browse files
committed
QR code from the staff equipment page, made tcket modal a little nicer on mobile, added the button on the user info page to report an issue if that user ls staff
1 parent c96c97d commit 75b2ad1

3 files changed

Lines changed: 123 additions & 12 deletions

File tree

client/src/pages/makerspace_page/equipment_pages/EquipmentUserInfo.tsx

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import PersonIcon from '@mui/icons-material/Person';
22
import { useParams } from "react-router-dom";
33
import { GET_EQUIPMENT_BY_ID } from "../../../queries/equipmentQueries";
4-
import { Alert, Button, CardActionArea, LinearProgress, Typography } from "@mui/material";
4+
import { Alert, Button, CardActionArea, LinearProgress, Link, Typography } from "@mui/material";
55
import { useQuery } from "@apollo/client/react";
66
import { Equipment } from "./ManageEquipmentPage";
77
import { Stack } from "@mui/system";
@@ -11,20 +11,24 @@ import { FullMakerspace, GET_MAKERSPACE_BY_ID } from "../../../queries/makerspac
1111
import { GET_ROOM } from "../../../queries/roomQueries";
1212
import RequestWrapper from "../../../common/RequestWrapper";
1313
import Room from "../../../types/Room";
14-
import { ReactNode } from "react";
14+
import { ReactNode, useState } from "react";
1515
import { IS_USER_WELCOMED } from "../../../queries/userQueries";
1616
import ModuleStatusRow from "../../../common/ModuleStatusRow";
1717
import RadioButtonUncheckedIcon from "@mui/icons-material/RadioButtonUnchecked";
1818
import CloseIcon from "@mui/icons-material/Close";
1919
import CheckIcon from '@mui/icons-material/Check';
20-
import { Link } from "react-router-dom";
20+
import WarningIcon from '@mui/icons-material/Warning';
21+
import NewTicketModal from '../maintenance_pages/NewTicketModal';
22+
import { isStaffFor } from '../../../common/PrivilegeUtils';
23+
2124

2225
export default function EquipmentUserInfo() {
2326
const user = useCurrentUser();
2427
const isVisitor = user.visitor;
2528

2629

2730
const { makerspaceID, equipmentID } = useParams<{ makerspaceID: string, equipmentID: string }>();
31+
const isStaffForHere = isStaffFor(user, Number(makerspaceID));
2832
const getEquipmentByIDResult = useQuery(GET_EQUIPMENT_BY_ID, {
2933
variables: {
3034
id: equipmentID,
@@ -48,6 +52,12 @@ export default function EquipmentUserInfo() {
4852
}
4953
});
5054

55+
const getMakerspace = useQuery(GET_MAKERSPACE_BY_ID, { variables: { id: makerspaceID } });
56+
57+
58+
const [newTicketModal, setNewTicketModal] = useState(false);
59+
60+
5161
function unfinishedTrainingWarning(): ReactNode {
5262
return <Alert severity="warning" title="Unfinished Training">
5363
<Stack>
@@ -209,12 +219,26 @@ export default function EquipmentUserInfo() {
209219
: null
210220
}
211221
</Stack>
212-
<Button
213-
color="info"
214-
variant="contained"
215-
onClick={() => window.open(equipment.sopUrl, "_blank")}
222+
<Stack direction="row" spacing="10px">
223+
<Button
224+
color="info"
225+
variant="contained"
226+
onClick={() => window.open(equipment.sopUrl, "_blank")}
227+
>
228+
Equipment Information
229+
</Button>
230+
231+
{isStaffForHere ? <Button
232+
color="primary"
233+
variant="contained"
234+
onClick={() => setNewTicketModal(true)}
235+
startIcon={<WarningIcon />}
236+
>
237+
Report Issue
238+
</Button> : undefined}
239+
</Stack>
240+
216241

217-
>Equipment Information</Button>
218242
</Stack>
219243
}
220244

@@ -233,6 +257,8 @@ export default function EquipmentUserInfo() {
233257
>
234258
LOGIN
235259
</Button>
260+
261+
236262
</Stack>
237263

238264
}
@@ -244,5 +270,7 @@ export default function EquipmentUserInfo() {
244270
? renderVisitor(getEquipmentByIDResult?.data?.equipment as Equipment)
245271
: renderPage(isWelcomedResult?.data?.isUserWelcomed ? isWelcomedResult?.data?.isUserWelcomed : false, getEquipmentByIDResult?.data?.equipment as Equipment, getRoomResult.data?.room as Room, getMakerspaceResult.data?.makerspaceByID as FullMakerspace)
246272
}
273+
{getMakerspace?.data?.makerspaceByID ? <NewTicketModal open={newTicketModal} onClose={() => setNewTicketModal(false)} makerspace={getMakerspace?.data?.makerspaceByID} /> : undefined}
274+
247275
</RequestWrapper>
248276
}

client/src/pages/makerspace_page/equipment_pages/ManageEquipmentPage.tsx

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { useMutation, useQuery } from "@apollo/client/react";
2-
import { Button, Stack, TextField, Typography } from "@mui/material";
2+
import { Button, Link, Stack, TextField, Typography } from "@mui/material";
33
import { GET_EQUIPMENT_BY_ID } from "../../../queries/equipmentQueries";
44
import { useNavigate, useParams } from "react-router-dom";
55
import RequestWrapper2 from "../../../common/RequestWrapper2";
66
import EquipmentInformation from "./EquipmentInformation";
77
import PrettyModal from "../../../common/PrettyModal";
88
import InstanceGrid from "./InstanceGrid";
99
import { useIsMobile } from "../../../common/IsMobileProvider";
10-
import { useState } from "react";
10+
import { useRef, useState } from "react";
1111
import AddIcon from '@mui/icons-material/Add';
1212
import { CREATE_EQUIPMENT_INSTANCE } from "../../../queries/equipmentInstanceQueries";
1313
import HistoryIcon from '@mui/icons-material/History';
@@ -18,6 +18,8 @@ import MaintenanceTicketGrid from "../maintenance_pages/MaintenanceTicketGrid";
1818
import WarningIcon from '@mui/icons-material/Warning';
1919
import NewTicketModal from "../maintenance_pages/NewTicketModal";
2020
import { useMakeTheme } from "../../../common/MakeThemeProvider";
21+
import QrCodeIcon from '@mui/icons-material/QrCode';
22+
import { QRCode } from "react-qr-code";
2123

2224
export interface Equipment {
2325
id: number;
@@ -51,13 +53,57 @@ export interface Equipment {
5153

5254
}
5355

56+
57+
58+
59+
5460
export default function ManageEquipmentPage() {
5561
const { makerspaceID, equipmentID } = useParams<{ makerspaceID: string, equipmentID: string }>();
5662
const isMobile = useIsMobile();
5763
const navigate = useNavigate();
5864
const user = useCurrentUser();
5965
const makeTheme = useMakeTheme();
6066

67+
const qrRef = useRef(null);
68+
69+
function copyQRCode() {
70+
try {
71+
const svg = qrRef.current;
72+
if (svg === null) { return; }
73+
const svgData = new XMLSerializer().serializeToString(svg);
74+
const canvas = document.createElement("canvas");
75+
const ctx = canvas.getContext("2d");
76+
const img = new Image();
77+
console.log("qrRef", qrRef.current)
78+
79+
const svgBlob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" });
80+
const url = URL.createObjectURL(svgBlob);
81+
82+
img.onload = () => {
83+
// Set canvas dimensions to match SVG
84+
canvas.width = img.width;
85+
canvas.height = img.height;
86+
if (ctx === null) { return; }
87+
ctx.drawImage(img, 0, 0);
88+
89+
// Copy to clipboard as a PNG
90+
canvas.toBlob((blob) => {
91+
if (blob === null) { return; }
92+
const item = new ClipboardItem({ "image/png": blob });
93+
navigator.clipboard.write([item]);
94+
});
95+
96+
URL.revokeObjectURL(url);
97+
};
98+
99+
img.src = url;
100+
} catch (err) {
101+
console.error("Failed to copy qr code", err)
102+
}
103+
}
104+
105+
106+
61107
const getEquipmentByIDResult = useQuery(GET_EQUIPMENT_BY_ID, {
62108
variables: {
63109
id: equipmentID,
@@ -70,6 +116,7 @@ export default function ManageEquipmentPage() {
70116

71117
const [newInstanceName, setNewInstanceName] = useState("");
72118
const [newInstanceModal, setNewInstanceModal] = useState(false);
119+
const [showQrCode, setShowQrCode] = useState<boolean>(false);
73120

74121
const [editEquipmentModal, setEditEquipmentModal] = useState(false);
75122

@@ -104,6 +151,19 @@ export default function ManageEquipmentPage() {
104151
>
105152
<Typography variant={isMobile ? "h5" : "h3"}>{`Manage ${equipment.name}`}</Typography>
106153
<Stack direction={"row"} spacing={1}>
154+
155+
<Button
156+
color="info"
157+
variant="contained"
158+
startIcon={<QrCodeIcon />}
159+
onClick={() => setShowQrCode(true)}
160+
sx={{
161+
justifySelf: "flex-end"
162+
}}
163+
>
164+
User QR Code
165+
</Button>
166+
107167
<Button
108168
color="secondary"
109169
variant="contained"
@@ -174,7 +234,28 @@ export default function ManageEquipmentPage() {
174234
</Button>
175235
</Stack>
176236
<MaintenanceTicketGrid equipmentID={equipment.id} />
237+
238+
239+
{/* Equipment User Info QR Code Modal */}
240+
<PrettyModal open={showQrCode} onClose={() => setShowQrCode(false)} elevation={7}>
241+
<Stack alignItems={"center"} spacing="10px">
242+
<Link
243+
href={`${import.meta.env.VITE_ORIGIN}/app/makerspace/${makerspaceID}/equipmentUserInfo/${equipmentID}`}
244+
target={"_blank"}
245+
>User Info Page</Link>
246+
<QRCode ref={qrRef} value={`${import.meta.env.VITE_ORIGIN}/app/makerspace/${makerspaceID}/equipmentUserInfo/${equipmentID}`} />
247+
<Button variant="contained" color="info" onClick={copyQRCode} >Copy</Button>
248+
</Stack>
249+
</PrettyModal>
250+
251+
252+
177253
</Stack>
254+
255+
256+
257+
258+
178259
{/* Equipment Information Modal */}
179260
<PrettyModal open={editEquipmentModal} onClose={() => setEditEquipmentModal(false)} width={"90%"} elevation={8}>
180261
<EquipmentInformation equipment={equipment} />

client/src/pages/makerspace_page/maintenance_pages/NewTicketModal.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { toast } from "react-toastify";
1313
import FileUploadButton from "../../../common/FileUploadButton";
1414
import styled from "styled-components";
1515
import { makeCDNLink } from "../../../common/ImageFinder";
16+
import { useIsMobile } from "../../../common/IsMobileProvider";
1617

1718
const StyledImg = styled.img`
1819
padding: 5px
@@ -27,6 +28,7 @@ interface NewTicketModalProps {
2728

2829
export default function NewTicketModal(props: NewTicketModalProps) {
2930
const user = useCurrentUser();
31+
const isMobile = useIsMobile();
3032

3133
const [equipment, setEquipment] = useState(props.equipment);
3234
const [instance, setInstance] = useState<EquipmentInstance>();
@@ -93,7 +95,7 @@ export default function NewTicketModal(props: NewTicketModalProps) {
9395
}
9496

9597
return (
96-
<PrettyModal open={props.open} onClose={handleClose} width={"620px"}>
98+
<PrettyModal open={props.open} onClose={handleClose} width={isMobile ? "90%" : "620px"}>
9799
<Stack spacing={2} padding={"10px"}>
98100
<Stack direction={"row"} justifyContent={"space-between"}>
99101
<Typography variant="h6">Create a Maintenance Ticket</Typography>
@@ -186,7 +188,7 @@ export default function NewTicketModal(props: NewTicketModalProps) {
186188
? <StyledImg src={makeCDNLink(imageUrl, "user-uploads/")} />
187189
: null
188190
}
189-
<Stack direction={"row"} justifyContent={"space-between"}>
191+
<Stack direction={isMobile ? "column" : "row"} spacing="2px" justifyContent={"space-between"}>
190192
<Button
191193
color="error"
192194
variant="contained"

0 commit comments

Comments
 (0)