From 7ef2d370ef52c4caf7db135c66e33dc064d8f718 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Max=20R=C3=BCdiger?= <maxru105@student.liu.se>
Date: Wed, 28 Apr 2021 09:01:44 +0000
Subject: [PATCH] 139 operator view

---
 client/src/Main.tsx                           |   8 +-
 .../admin/competitions/CompetitionManager.tsx |   2 +-
 ...age.test.tsx => OperatorViewPage.test.tsx} |   4 +-
 ...enterViewPage.tsx => OperatorViewPage.tsx} | 182 +++++++++++++-----
 ...iewPage.test.tsx => TeamViewPage.test.tsx} |   4 +-
 ...rticipantViewPage.tsx => TeamViewPage.tsx} |  14 +-
 client/src/pages/views/ViewSelectPage.tsx     |   6 +-
 client/src/pages/views/styled.tsx             |  14 +-
 8 files changed, 160 insertions(+), 74 deletions(-)
 rename client/src/pages/views/{PresenterViewPage.test.tsx => OperatorViewPage.test.tsx} (91%)
 rename client/src/pages/views/{PresenterViewPage.tsx => OperatorViewPage.tsx} (53%)
 rename client/src/pages/views/{ParticipantViewPage.test.tsx => TeamViewPage.test.tsx} (85%)
 rename client/src/pages/views/{ParticipantViewPage.tsx => TeamViewPage.tsx} (77%)

diff --git a/client/src/Main.tsx b/client/src/Main.tsx
index b999a977..b1f0675d 100644
--- a/client/src/Main.tsx
+++ b/client/src/Main.tsx
@@ -7,8 +7,8 @@ import LoginPage from './pages/login/LoginPage'
 import PresentationEditorPage from './pages/presentationEditor/PresentationEditorPage'
 import AudienceViewPage from './pages/views/AudienceViewPage'
 import JudgeViewPage from './pages/views/JudgeViewPage'
-import ParticipantViewPage from './pages/views/ParticipantViewPage'
-import PresenterViewPage from './pages/views/PresenterViewPage'
+import TeamViewPage from './pages/views/TeamViewPage'
+import OperatorViewPage from './pages/views/OperatorViewPage'
 import ViewSelectPage from './pages/views/ViewSelectPage'
 import SecureRoute from './utils/SecureRoute'
 
@@ -24,8 +24,8 @@ const Main: React.FC = () => {
         <SecureRoute path="/admin" component={AdminPage} />
         <SecureRoute path="/editor/competition-id=:competitionId" component={PresentationEditorPage} />
         <Route exact path="/:code" component={ViewSelectPage} />
-        <Route exact path="/participant/id=:id&code=:code" component={ParticipantViewPage} />
-        <SecureRoute exact path="/presenter/id=:id&code=:code" component={PresenterViewPage} />
+        <Route exact path="/team/id=:id&code=:code" component={TeamViewPage} />
+        <SecureRoute exact path="/operator/id=:id&code=:code" component={OperatorViewPage} />
         <Route exact path="/judge/id=:id&code=:code" component={JudgeViewPage} />
         <Route exact path="/audience/id=:id&code=:code" component={AudienceViewPage} />
       </Switch>
diff --git a/client/src/pages/admin/competitions/CompetitionManager.tsx b/client/src/pages/admin/competitions/CompetitionManager.tsx
index 83f399b9..8ec3ee29 100644
--- a/client/src/pages/admin/competitions/CompetitionManager.tsx
+++ b/client/src/pages/admin/competitions/CompetitionManager.tsx
@@ -94,7 +94,7 @@ const CompetitionManager: React.FC = (props: any) => {
   }
 
   const handleStartCompetition = () => {
-    history.push(`/presenter/id=${activeId}&code=123123`)
+    history.push(`/operator/id=${activeId}&code=123123`)
     console.log('GLHF!')
   }
 
diff --git a/client/src/pages/views/PresenterViewPage.test.tsx b/client/src/pages/views/OperatorViewPage.test.tsx
similarity index 91%
rename from client/src/pages/views/PresenterViewPage.test.tsx
rename to client/src/pages/views/OperatorViewPage.test.tsx
index fd7b0a96..e658fe3b 100644
--- a/client/src/pages/views/PresenterViewPage.test.tsx
+++ b/client/src/pages/views/OperatorViewPage.test.tsx
@@ -4,7 +4,7 @@ import React from 'react'
 import { Provider } from 'react-redux'
 import { BrowserRouter } from 'react-router-dom'
 import store from '../../store'
-import PresenterViewPage from './PresenterViewPage'
+import OperatorViewPage from './OperatorViewPage'
 
 it('renders presenter view page', () => {
   const compRes: any = {
@@ -36,7 +36,7 @@ it('renders presenter view page', () => {
   render(
     <BrowserRouter>
       <Provider store={store}>
-        <PresenterViewPage />
+        <OperatorViewPage />
       </Provider>
     </BrowserRouter>
   )
diff --git a/client/src/pages/views/PresenterViewPage.tsx b/client/src/pages/views/OperatorViewPage.tsx
similarity index 53%
rename from client/src/pages/views/PresenterViewPage.tsx
rename to client/src/pages/views/OperatorViewPage.tsx
index 6c853a60..53af321c 100644
--- a/client/src/pages/views/PresenterViewPage.tsx
+++ b/client/src/pages/views/OperatorViewPage.tsx
@@ -7,6 +7,7 @@ import {
   DialogTitle,
   List,
   ListItem,
+  ListItemText,
   Popover,
   Tooltip,
   Typography,
@@ -14,6 +15,8 @@ import {
   useTheme,
 } from '@material-ui/core'
 import AssignmentIcon from '@material-ui/icons/Assignment'
+import FileCopyIcon from '@material-ui/icons/FileCopy'
+import SupervisorAccountIcon from '@material-ui/icons/SupervisorAccount'
 import BackspaceIcon from '@material-ui/icons/Backspace'
 import ChevronLeftIcon from '@material-ui/icons/ChevronLeft'
 import ChevronRightIcon from '@material-ui/icons/ChevronRight'
@@ -36,23 +39,39 @@ import SlideDisplay from '../presentationEditor/components/SlideDisplay'
 import PresentationComponent from './components/PresentationComponent'
 import Timer from './components/Timer'
 import {
-  PresenterButton,
-  PresenterContainer,
-  PresenterContent,
-  PresenterFooter,
-  PresenterHeader,
-  PresenterInnerContent,
+  OperatorButton,
+  OperatorContainer,
+  OperatorFooter,
+  OperatorHeader,
+  OperatorContent,
+  OperatorInnerContent,
   SlideCounter,
   ToolBarContainer,
 } from './styled'
 
 /**
+ *  Description:
+ *
  *  Presentation is an active competition
+ *
+ *
+ *  ===========================================
+ *  TODO:
+ *  - Instead of copying code for others to join the competition, copy URL.
+ *
+ *  - Make code popup less code by using .map instead
+ *
+ *  - Fix scoreboard
+ *
+ *  - When two userers are connected to the same Localhost:5000 and updates/starts/end competition it
+ *    creates a bug where the competition can't be started.
+ * ===========================================
  */
 
-const PresenterViewPage: React.FC = () => {
+const OperatorViewPage: React.FC = () => {
   // for dialog alert
   const [openAlert, setOpen] = React.useState(false)
+  const [openAlertCode, setOpenCode] = React.useState(true)
   const theme = useTheme()
   const fullScreen = useMediaQuery(theme.breakpoints.down('sm'))
   const teams = useAppSelector((state) => state.presentation.competition.teams)
@@ -62,14 +81,14 @@ const PresenterViewPage: React.FC = () => {
   const history = useHistory()
   const dispatch = useAppDispatch()
   const viewTypes = useAppSelector((state) => state.types.viewTypes)
-  const activeViewTypeId = viewTypes.find((viewType) => viewType.name === 'Presenter')?.id
+  const activeViewTypeId = viewTypes.find((viewType) => viewType.name === 'Operator')?.id
 
   useEffect(() => {
     dispatch(getPresentationCompetition(id))
     dispatch(setPresentationCode(code))
     socket_connect()
     socketSetSlide // Behövs denna?
-    setTimeout(startCompetition, 500) // Ghetto, wait for everything to load
+    setTimeout(startCompetition, 1000) // Ghetto, wait for everything to load
     // console.log(id)
   }, [])
 
@@ -79,6 +98,7 @@ const PresenterViewPage: React.FC = () => {
 
   const handleClose = () => {
     setOpen(false)
+    setOpenCode(false)
     setAnchorEl(null)
   }
 
@@ -92,6 +112,14 @@ const PresenterViewPage: React.FC = () => {
     setOpen(true)
   }
 
+  const handleOpenCodes = () => {
+    setOpenCode(true)
+  }
+
+  const handleCopy = () => {
+    console.log('copied code to clipboard')
+  }
+
   const endCompetition = () => {
     setOpen(false)
     socketEndPresentation()
@@ -100,12 +128,66 @@ const PresenterViewPage: React.FC = () => {
   }
 
   return (
-    <PresenterContainer>
-      <PresenterHeader>
+    <OperatorContainer>
+      <Dialog
+        fullScreen={fullScreen}
+        open={openAlertCode}
+        onClose={handleClose}
+        aria-labelledby="responsive-dialog-title"
+      >
+        <DialogTitle id="responsive-dialog-title">{'Koder för tävlingen'}</DialogTitle>
+        <DialogContent>
+          <DialogContentText>
+            <ListItem>
+              <ListItemText primary={`Domare: ${presentation.code}`} />
+              <Tooltip title="Kopiera kod" arrow>
+                <Button
+                  onClick={() => {
+                    navigator.clipboard.writeText(presentation.code)
+                  }}
+                >
+                  <FileCopyIcon fontSize="small" />
+                </Button>
+              </Tooltip>
+            </ListItem>
+            <ListItem>
+              <ListItemText primary={`Tävlande: ${presentation.code}`} />
+              <Tooltip title="Kopiera kod" arrow>
+                <Button
+                  onClick={() => {
+                    navigator.clipboard.writeText(presentation.code)
+                  }}
+                >
+                  <FileCopyIcon fontSize="small" />
+                </Button>
+              </Tooltip>
+            </ListItem>
+            <ListItem>
+              <ListItemText primary={`Publik: ${presentation.code}`} />
+              <Tooltip title="Kopiera kod" arrow>
+                <Button
+                  onClick={() => {
+                    navigator.clipboard.writeText(presentation.code)
+                  }}
+                >
+                  <FileCopyIcon fontSize="small" />
+                </Button>
+              </Tooltip>
+            </ListItem>
+          </DialogContentText>
+        </DialogContent>
+        <DialogActions>
+          <Button autoFocus onClick={handleClose} color="primary">
+            Ok
+          </Button>
+        </DialogActions>
+      </Dialog>
+
+      <OperatorHeader>
         <Tooltip title="Avsluta tävling" arrow>
-          <PresenterButton onClick={handleVerifyExit} variant="contained" color="secondary">
+          <OperatorButton onClick={handleVerifyExit} variant="contained" color="secondary">
             <BackspaceIcon fontSize="large" />
-          </PresenterButton>
+          </OperatorButton>
         </Tooltip>
 
         <Dialog
@@ -135,67 +217,73 @@ const PresenterViewPage: React.FC = () => {
             {presentation.slide.order + 1} / {presentation.competition.slides.length}
           </Typography>
         </SlideCounter>
-      </PresenterHeader>
+      </OperatorHeader>
       <div style={{ height: 0, paddingTop: 120 }} />
-      <PresenterContent>
-        <PresenterInnerContent>
+      <OperatorContent>
+        <OperatorInnerContent>
           {activeViewTypeId && <SlideDisplay variant="presentation" activeViewTypeId={activeViewTypeId} />}
-        </PresenterInnerContent>
-      </PresenterContent>
+        </OperatorInnerContent>
+      </OperatorContent>
       <div style={{ height: 0, paddingTop: 140 }} />
-      <PresenterFooter>
+      <OperatorFooter>
         <ToolBarContainer>
-          <Tooltip title="Previous Slide" arrow>
-            <PresenterButton onClick={socketSetSlidePrev} variant="contained">
+          <Tooltip title="Föregående" arrow>
+            <OperatorButton onClick={socketSetSlidePrev} variant="contained">
               <ChevronLeftIcon fontSize="large" />
-            </PresenterButton>
+            </OperatorButton>
           </Tooltip>
 
           {/* 
           // Manual start button
           <Tooltip title="Start Presentation" arrow>
-            <PresenterButton onClick={startCompetition} variant="contained">
+            <OperatorButton onClick={startCompetition} variant="contained">
               start
-            </PresenterButton>
+            </OperatorButton>
           </Tooltip>
 
           
-          // This creates a join button, but presenter should not join others, others should join presenter
+          // This creates a join button, but Operator should not join others, others should join Operator
           <Tooltip title="Join Presentation" arrow>
-            <PresenterButton onClick={socketJoinPresentation} variant="contained">
+            <OperatorButton onClick={socketJoinPresentation} variant="contained">
               <GroupAddIcon fontSize="large" />
-            </PresenterButton>
+            </OperatorButton>
           </Tooltip>
           
 
           // This creates another end button, it might not be needed since we already have one
           <Tooltip title="End Presentation" arrow>
-            <PresenterButton onClick={socketEndPresentation} variant="contained">
+            <OperatorButton onClick={socketEndPresentation} variant="contained">
               <CancelIcon fontSize="large" />
-            </PresenterButton>
+            </OperatorButton>
           </Tooltip>
           */}
 
-          <Tooltip title="Start Timer" arrow>
-            <PresenterButton onClick={socketStartTimer} variant="contained">
+          <Tooltip title="Starta Timer" arrow>
+            <OperatorButton onClick={socketStartTimer} variant="contained">
               <TimerIcon fontSize="large" />
               <Timer></Timer>
-            </PresenterButton>
+            </OperatorButton>
           </Tooltip>
 
-          <Tooltip title="Scoreboard" arrow>
-            <PresenterButton onClick={handleOpenPopover} variant="contained">
+          <Tooltip title="Ställning" arrow>
+            <OperatorButton onClick={handleOpenPopover} variant="contained">
               <AssignmentIcon fontSize="large" />
-            </PresenterButton>
+            </OperatorButton>
           </Tooltip>
 
-          <Tooltip title="Next Slide" arrow>
-            <PresenterButton onClick={socketSetSlideNext} variant="contained">
+          <Tooltip title="Koder" arrow>
+            <OperatorButton onClick={handleOpenCodes} variant="contained">
+              <SupervisorAccountIcon fontSize="large" />
+            </OperatorButton>
+          </Tooltip>
+
+          <Tooltip title="Nästa" arrow>
+            <OperatorButton onClick={socketSetSlideNext} variant="contained">
               <ChevronRightIcon fontSize="large" />
-            </PresenterButton>
+            </OperatorButton>
           </Tooltip>
         </ToolBarContainer>
-      </PresenterFooter>
+      </OperatorFooter>
       <Popover
         open={Boolean(anchorEl)}
         anchorEl={anchorEl}
@@ -210,17 +298,15 @@ const PresenterViewPage: React.FC = () => {
         }}
       >
         <List>
-          {/**  TODO:
-           *    Fix scoreboard
-           */}
-          {teams && teams.map((team) => <ListItem key={team.id}>{team.name} score: 20</ListItem>)}
+          {teams.map((team) => (
+            <ListItem key={team.id}>
+              {team.name} score: {team.question_answers}{' '}
+            </ListItem>
+          ))}
         </List>
       </Popover>
-    </PresenterContainer>
+    </OperatorContainer>
   )
 }
 
-export default PresenterViewPage
-function componentDidMount() {
-  throw new Error('Function not implemented.')
-}
+export default OperatorViewPage
diff --git a/client/src/pages/views/ParticipantViewPage.test.tsx b/client/src/pages/views/TeamViewPage.test.tsx
similarity index 85%
rename from client/src/pages/views/ParticipantViewPage.test.tsx
rename to client/src/pages/views/TeamViewPage.test.tsx
index e25ab6b9..10574f7e 100644
--- a/client/src/pages/views/ParticipantViewPage.test.tsx
+++ b/client/src/pages/views/TeamViewPage.test.tsx
@@ -3,7 +3,7 @@ import React from 'react'
 import { Provider } from 'react-redux'
 import { BrowserRouter } from 'react-router-dom'
 import store from '../../store'
-import ParticipantViewPage from './ParticipantViewPage'
+import TeamViewPage from './TeamViewPage'
 import mockedAxios from 'axios'
 
 it('renders participant view page', () => {
@@ -16,7 +16,7 @@ it('renders participant view page', () => {
   render(
     <BrowserRouter>
       <Provider store={store}>
-        <ParticipantViewPage />
+        <TeamViewPage />
       </Provider>
     </BrowserRouter>
   )
diff --git a/client/src/pages/views/ParticipantViewPage.tsx b/client/src/pages/views/TeamViewPage.tsx
similarity index 77%
rename from client/src/pages/views/ParticipantViewPage.tsx
rename to client/src/pages/views/TeamViewPage.tsx
index 0c4db2c0..32eef28b 100644
--- a/client/src/pages/views/ParticipantViewPage.tsx
+++ b/client/src/pages/views/TeamViewPage.tsx
@@ -2,28 +2,28 @@ import React, { useEffect } from 'react'
 import PresentationComponent from './components/PresentationComponent'
 import { useHistory } from 'react-router-dom'
 import SlideDisplay from '../presentationEditor/components/SlideDisplay'
-import { ParticipantContainer } from './styled'
+import { TeamContainer } from './styled'
 import { socketJoinPresentation, socket_connect } from '../../sockets'
 import { useAppSelector } from '../../hooks'
 
-const ParticipantViewPage: React.FC = () => {
+const TeamViewPage: React.FC = () => {
   const history = useHistory()
   const code = useAppSelector((state) => state.presentation.code)
   const viewTypes = useAppSelector((state) => state.types.viewTypes)
-  const activeViewTypeId = viewTypes.find((viewType) => viewType.name === 'Participant')?.id
+  const activeViewTypeId = viewTypes.find((viewType) => viewType.name === 'Team')?.id
   useEffect(() => {
     //hides the url so people can't sneak peak
-    history.push('participant')
+    history.push('team')
     if (code && code !== '') {
       socket_connect()
       socketJoinPresentation()
     }
   }, [])
   return (
-    <ParticipantContainer>
+    <TeamContainer>
       {activeViewTypeId && <SlideDisplay variant="presentation" activeViewTypeId={activeViewTypeId} />}
-    </ParticipantContainer>
+    </TeamContainer>
   )
 }
 
-export default ParticipantViewPage
+export default TeamViewPage
diff --git a/client/src/pages/views/ViewSelectPage.tsx b/client/src/pages/views/ViewSelectPage.tsx
index 686ce4f1..845ba21c 100644
--- a/client/src/pages/views/ViewSelectPage.tsx
+++ b/client/src/pages/views/ViewSelectPage.tsx
@@ -4,9 +4,9 @@ import { Link, useRouteMatch } from 'react-router-dom'
 import { ViewSelectButtonGroup, ViewSelectContainer } from './styled'
 import { useParams } from 'react-router-dom'
 import { CircularProgress, Typography } from '@material-ui/core'
-import ParticipantViewPage from './ParticipantViewPage'
+import TeamViewPage from './TeamViewPage'
 import axios from 'axios'
-import PresenterViewPage from './PresenterViewPage'
+import OperatorViewPage from './OperatorViewPage'
 import JudgeViewPage from './JudgeViewPage'
 import AudienceViewPage from './AudienceViewPage'
 import { useAppDispatch, useAppSelector } from '../../hooks'
@@ -29,7 +29,7 @@ const ViewSelectPage: React.FC = () => {
     if (competitionId) {
       switch (viewType) {
         case 'Team':
-          return <ParticipantViewPage />
+          return <TeamViewPage />
         case 'Judge':
           return <JudgeViewPage code={code} competitionId={competitionId} />
         case 'Audience':
diff --git a/client/src/pages/views/styled.tsx b/client/src/pages/views/styled.tsx
index 8f63892a..96999027 100644
--- a/client/src/pages/views/styled.tsx
+++ b/client/src/pages/views/styled.tsx
@@ -35,7 +35,7 @@ export const ViewSelectButtonGroup = styled.div`
   margin-right: auto;
 `
 
-export const PresenterHeader = styled.div`
+export const OperatorHeader = styled.div`
   display: flex;
   justify-content: space-between;
   height: 120px;
@@ -43,7 +43,7 @@ export const PresenterHeader = styled.div`
   position: absolute;
 `
 
-export const PresenterFooter = styled.div`
+export const OperatorFooter = styled.div`
   display: flex;
   justify-content: space-between;
   height: 140px;
@@ -52,7 +52,7 @@ export const PresenterFooter = styled.div`
   width: 100%;
 `
 
-export const PresenterButton = styled(Button)`
+export const OperatorButton = styled(Button)`
   width: 100px;
   height: 100px;
   margin-left: 16px;
@@ -66,7 +66,7 @@ export const SlideCounter = styled(Button)`
   margin-top: 16px;
 `
 
-export const PresenterContainer = styled.div`
+export const OperatorContainer = styled.div`
   display: flex;
   flex-direction: column;
   justify-content: space-between;
@@ -127,7 +127,7 @@ export const InnerContent = styled.div`
   max-width: calc(((100vh - 64px) / 9) * 16);
 `
 
-export const PresenterContent = styled.div`
+export const OperatorContent = styled.div`
   height: 100%;
   width: 100%;
   display: flex;
@@ -135,7 +135,7 @@ export const PresenterContent = styled.div`
   background-color: rgba(0, 0, 0, 0.08);
 `
 
-export const PresenterInnerContent = styled.div`
+export const OperatorInnerContent = styled.div`
   height: 100%;
   width: 100%;
   /* Makes sure width is not bigger than where a 16:9 display can fit 
@@ -143,7 +143,7 @@ export const PresenterInnerContent = styled.div`
   max-width: calc(((100vh - 260px) / 9) * 16);
 `
 
-export const ParticipantContainer = styled.div`
+export const TeamContainer = styled.div`
   max-width: calc((100vh / 9) * 16);
 `
 
-- 
GitLab