1+ import PersonIcon from '@mui/icons-material/Person' ;
2+ import { useParams } from "react-router-dom" ;
3+ import { GET_EQUIPMENT_BY_ID } from "../../../queries/equipmentQueries" ;
4+ import { Alert , Button , CardActionArea , LinearProgress , Typography } from "@mui/material" ;
5+ import { useQuery } from "@apollo/client/react" ;
6+ import { Equipment } from "./ManageEquipmentPage" ;
7+ import { Stack } from "@mui/system" ;
8+ import { useCurrentUser } from "../../../common/CurrentUserProvider" ;
9+ import { ModuleStatus , moduleStatusMapper , TrainingModule } from "../../../common/TrainingModuleUtils" ;
10+ import { FullMakerspace , GET_MAKERSPACE_BY_ID } from "../../../queries/makerspaceQueries" ;
11+ import { GET_ROOM } from "../../../queries/roomQueries" ;
12+ import RequestWrapper from "../../../common/RequestWrapper" ;
13+ import Room from "../../../types/Room" ;
14+ import { ReactNode } from "react" ;
15+ import { IS_USER_WELCOMED } from "../../../queries/userQueries" ;
16+ import ModuleStatusRow from "../../../common/ModuleStatusRow" ;
17+ import RadioButtonUncheckedIcon from "@mui/icons-material/RadioButtonUnchecked" ;
18+ import CloseIcon from "@mui/icons-material/Close" ;
19+ import CheckIcon from '@mui/icons-material/Check' ;
20+ import { Link } from "react-router-dom" ;
21+
22+ export default function EquipmentUserInfo ( ) {
23+ const user = useCurrentUser ( ) ;
24+ const isVisitor = user . visitor ;
25+
26+
27+ const { makerspaceID, equipmentID } = useParams < { makerspaceID : string , equipmentID : string } > ( ) ;
28+ const getEquipmentByIDResult = useQuery ( GET_EQUIPMENT_BY_ID , {
29+ variables : {
30+ id : equipmentID ,
31+ } ,
32+ } ) ;
33+ const getMakerspaceResult = useQuery ( GET_MAKERSPACE_BY_ID , {
34+ variables : {
35+ id : makerspaceID ,
36+ } ,
37+ } ) ;
38+
39+ const getRoomResult = useQuery ( GET_ROOM , {
40+ variables : {
41+ id : getEquipmentByIDResult . data ?. equipment ?. room ?. id ?? - 1 ,
42+ }
43+ } ) ;
44+ const isWelcomedResult = useQuery ( IS_USER_WELCOMED , {
45+ variables : {
46+ userID : user . id ,
47+ roomID : getEquipmentByIDResult ?. data ?. equipment ?. room ?. id ?? - 1 ,
48+ }
49+ } ) ;
50+
51+ function unfinishedTrainingWarning ( ) : ReactNode {
52+ return < Alert severity = "warning" title = "Unfinished Training" >
53+ < Stack >
54+ < Typography variant = "body1" >
55+ It looks like you haven't finished your trainings yet.
56+ Scroll down to see what's left.
57+ </ Typography >
58+ </ Stack >
59+ </ Alert >
60+ }
61+ function noInPersonAccessCheck ( ) : ReactNode {
62+ return < Alert severity = "warning" title = "No access check" >
63+ < Stack >
64+ < Typography variant = "body1" >
65+ Almost there! Before you unlock the machine, you must take an in person access check. Talk to a staff member to start this process.
66+ </ Typography >
67+ </ Stack >
68+ </ Alert >
69+ }
70+ function unwelcomedWarning ( ) : ReactNode {
71+ return < Alert severity = "warning" title = "Not yet signed" >
72+ It looks like you haven't signed in today. Tap your card at the front desk to sign in to the space.
73+ </ Alert >
74+ }
75+ function allGood ( ) : ReactNode {
76+ return < Alert severity = "success" >
77+ Qualifications complete! Talk to staff if you're still having issues.
78+ </ Alert >
79+ }
80+
81+
82+
83+ function renderPage ( isWelcomed : boolean | undefined , equipment : Equipment | undefined , room : Room | undefined , makerspace : FullMakerspace | undefined ) {
84+
85+ if ( ! equipment || ! room || ! makerspace ) {
86+ return < Alert severity = "error" > Failed to load equipment requirements</ Alert >
87+ }
88+
89+ const hasApprovedAccessCheck : boolean = user . accessChecks . some ( ( ac ) => Number ( ac . equipmentID ) === Number ( equipment . id ) && ac . approved )
90+
91+ const makerspaceStatuses : ModuleStatus [ ] = makerspace . trainingModules . map ( moduleStatusMapper ( user . passedModules , user . trainingHolds ) ) ;
92+ const roomStatuses : ModuleStatus [ ] = room . trainingModules . map ( moduleStatusMapper ( user . passedModules , user . trainingHolds ) ) ;
93+ const equipmentStatuses : ModuleStatus [ ] = equipment . trainingModules . map ( ( obj ) => moduleStatusMapper ( user . passedModules , user . trainingHolds ) ( obj as TrainingModule ) ) ;
94+
95+ const numMakerspaceTrainingsComplete : number = makerspaceStatuses . filter ( ( module ) => module . status === "Passed" || module . status === "Expiring Soon" ) . length ;
96+ const numRoomTrainingsComplete : number = roomStatuses . filter ( ( module ) => module . status === "Passed" || module . status === "Expiring Soon" ) . length ;
97+ const numEquipmentTrainingsComplete : number = equipmentStatuses . filter ( ( module ) => module . status === "Passed" || module . status === "Expiring Soon" ) . length
98+
99+ const byExpiry = [ ...makerspaceStatuses , ...roomStatuses , ...equipmentStatuses ]
100+ . filter ( ( module ) => module . status === "Expiring Soon" || module . status === "Passed" )
101+ . sort ( ( a , b ) => new Date ( a . expirationDate ) . getTime ( ) - new Date ( b . expirationDate ) . getTime ( ) ) ;
102+
103+ const totalRequirements = makerspaceStatuses . length + roomStatuses . length + equipmentStatuses . length + ( equipment . requiresInPerson ? 1 : 0 ) ;
104+ const totalReqsComplete = numMakerspaceTrainingsComplete + numRoomTrainingsComplete + numEquipmentTrainingsComplete + ( ( hasApprovedAccessCheck && equipment . requiresInPerson ) ? 1 : 0 ) ;
105+
106+ const percentComplete : number = Math . round ( totalReqsComplete / totalRequirements * 100 ) ;
107+
108+
109+ let warningMessage : ( ) => ReactNode = unfinishedTrainingWarning ;
110+ const hasAllTrainings = ( numMakerspaceTrainingsComplete + numRoomTrainingsComplete + numEquipmentTrainingsComplete ) >= makerspaceStatuses . length + roomStatuses . length + equipmentStatuses . length ;
111+ if ( ! hasAllTrainings ) {
112+ warningMessage = unfinishedTrainingWarning ;
113+ } else if ( ! hasApprovedAccessCheck && equipment . requiresInPerson ) {
114+ warningMessage = noInPersonAccessCheck ;
115+ } else if ( ! isWelcomed && equipment . needsWelcome ) {
116+ warningMessage = unwelcomedWarning ;
117+ } else {
118+ warningMessage = allGood ;
119+ }
120+
121+
122+ return < Stack padding = { "20px 20px 15px" } spacing = "10px" justifyContent = { "center" } alignItems = { "center" } display = { "flex" } >
123+ { warningMessage ( ) }
124+ < Typography variant = "h1" fontSize = "1.5em" fontWeight = { "400" } > { equipment . name } </ Typography >
125+ < LinearProgress
126+ variant = "determinate"
127+ value = { percentComplete }
128+ color = {
129+ totalReqsComplete !== totalRequirements
130+ ? "primary"
131+ : byExpiry . length > 0 && byExpiry [ 0 ] . status === "Expiring Soon"
132+ ? "warning"
133+ : "success"
134+ }
135+ sx = { {
136+ width : "95%" ,
137+ height : "16px"
138+ } }
139+ />
140+ < Typography variant = "subtitle1" fontWeight = { "bold" } display = { "item" } >
141+ {
142+ totalReqsComplete !== totalRequirements
143+ ? `Training ${ percentComplete } % Complete`
144+ : byExpiry . length > 0 && byExpiry [ 0 ] . status === "Expiring Soon"
145+ ? "Expiring Soon!"
146+ : "Trainings Complete!"
147+ }
148+ </ Typography >
149+
150+ < Stack spacing = { 2 } >
151+ {
152+ makerspace . trainingModules . length > 0
153+ ? < Stack >
154+ < Typography variant = "h6" > Makerspace Requirements</ Typography >
155+ {
156+ makerspaceStatuses . map ( ( moduleStatus ) => < ModuleStatusRow ms = { moduleStatus } /> )
157+ }
158+ </ Stack >
159+ : null
160+ }
161+ {
162+ room . trainingModules . length > 0
163+ ? < Stack >
164+ < Typography variant = "h6" > Area Requirements</ Typography >
165+ {
166+ roomStatuses . map ( ( moduleStatus ) => < ModuleStatusRow ms = { moduleStatus } /> )
167+ }
168+ </ Stack >
169+ : null
170+ }
171+ {
172+ ( equipment . trainingModules . length > 0 || equipment . requiresInPerson )
173+ ? < Stack >
174+ < Typography variant = "h6" > Equipment Requirements</ Typography >
175+ {
176+ equipmentStatuses . map ( ( moduleStatus ) => < ModuleStatusRow ms = { moduleStatus } /> )
177+ }
178+ {
179+ equipment . requiresInPerson
180+ ? < CardActionArea
181+ onClick = { equipment . signOffUrl ? ( ) => window . open ( equipment . signOffUrl , "_blank noopener noreferrer" ) : undefined }
182+ disableRipple = { equipment . signOffUrl === "" }
183+ >
184+ < Stack direction = { "row" } spacing = { 1 } alignItems = "center" padding = "7px" >
185+ { user . visitor ? (
186+ < RadioButtonUncheckedIcon color = "secondary" />
187+ ) : hasApprovedAccessCheck ? (
188+ < CheckIcon color = "success" />
189+ ) : (
190+ < CloseIcon color = "error" />
191+ ) }
192+ < Stack direction = { "column" } width = { "100%" } >
193+ {
194+ equipment . signOffUrl !== ""
195+ ? < Link variant = "body2" > Staff Sign-Off</ Link >
196+ : < Typography variant = "body2" > Staff Sign-Off</ Typography >
197+ }
198+ {
199+ ( equipment . requiresInPerson && ! hasApprovedAccessCheck )
200+ ? < Typography variant = "body2" > Complete all other requirments before attempting sign-off!</ Typography >
201+ : null
202+ }
203+ </ Stack >
204+ </ Stack >
205+ </ CardActionArea >
206+ : null
207+ }
208+ </ Stack >
209+ : null
210+ }
211+ </ Stack >
212+ < Button
213+ color = "info"
214+ variant = "contained"
215+ onClick = { ( ) => window . open ( equipment . sopUrl , "_blank" ) }
216+
217+ > Equipment Information</ Button >
218+ </ Stack >
219+ }
220+
221+ function renderVisitor ( equipment : Equipment | undefined ) {
222+ if ( ! equipment ) {
223+ return < Alert severity = "error" > Failed to load equipment information</ Alert >
224+ }
225+ return < Stack padding = "10px" spacing = "10px" >
226+ < Typography variant = "h1" fontSize = "1.5em" fontWeight = { "400" } > { equipment . name } </ Typography >
227+ To view equipment prerequisites, please log in.
228+ < Button
229+ variant = "contained"
230+ color = "secondary"
231+ endIcon = { < PersonIcon /> }
232+ onClick = { ( ) => window . location . replace ( import . meta. env . VITE_LOGIN_URL + "?redir=" + import . meta. env . VITE_ORIGIN + window . location . pathname ) }
233+ >
234+ LOGIN
235+ </ Button >
236+ </ Stack >
237+
238+ }
239+
240+ return < RequestWrapper loading = { getEquipmentByIDResult . loading || getMakerspaceResult . loading || getRoomResult . loading } error = { getEquipmentByIDResult . error || getMakerspaceResult . error || getRoomResult . error } minHeight = { 322 } >
241+ < title > { ( getEquipmentByIDResult ?. data ?. equipment as Equipment ) ?. name ?? "Equipment Checklist" } </ title >
242+ {
243+ isVisitor
244+ ? renderVisitor ( getEquipmentByIDResult ?. data ?. equipment as Equipment )
245+ : renderPage ( isWelcomedResult ?. data ?. isUserWelcomed ? isWelcomedResult ?. data ?. isUserWelcomed : false , getEquipmentByIDResult ?. data ?. equipment as Equipment , getRoomResult . data ?. room as Room , getMakerspaceResult . data ?. makerspaceByID as FullMakerspace )
246+ }
247+ </ RequestWrapper >
248+ }
0 commit comments